Preface

Welcome to Java for Python Programmers. This short ebook is an ongoing project to help Computer Science students who have had one or two semesters of Python learn the Java programming language. If you are not a part of that audience you may still find this a useful way to learn about Java. This book is written using the build on what you know philosophy. In order to help you learn Java I will start with a Python example and then implement the example in Java. Along the way we will examine the strengths, weaknesses and differences between those two languages.

This book does not attempt to replace the many good Java reference books that are available, in fact I use this in my course along with Horstman’s Core Java volumes. Please feel free to use this book for yourself, or if it fits a class you are teaching you are welcome to use this as a resource for your own class.

I have published this article using a Creative Commons license to encourage you to use it, change it, and modify it for your own purposes. I would appreciate knowing what you think if you do use this book, and I would love to see any modifications or additions you make.

Brad Miller bmiller@luther.edu January, 2008

image This work is licensed under a Creative Commons Attribution 3.0 United States License. See http://creativecommons.org

Introduction

This book assumes that you are already familiar with the Python programming language. We will use Python as a starting point for our journey into Java. We will begin by looking at a very simple Java program, just to see what the language looks like and how we get a program to run. Next, we will look at the main constructs that are common to most programming languages:

  • Data Types
  • Loops
  • Reading user input
  • Conditionals

Once we have the basics of Java behind us we will move on to look at the features of Java that are both unique and powerful.

  • Classes
  • Interfaces
  • Collections
  • Graphical User Interface Programming
  • Generic Programming

Please note that this book is a work in progress. I will continue to update and post new versions.

Why Learn another programming Language?

Python is a nice language for beginning programming for several reasons. First the syntax is sparse, and clear. Second, the underlying model of how objects and variables work is very consistent. Third, you can write powerful and interesting programs without a lot of work. However, Python is representative of one kind of language, called a dynamic language. You might think of Python as being fairly informal. There are other languages, like Java and C++ that are more formal.

These languages have some advantages of their own. First, is speed: For very large programs Java and C++ are going to give you the best performance. Second is their maintainability. A lot of what makes Python easy to use is that you must remember certain things. For example if you set variable x to reference a turtle, and forget later that x is a turtle but try to invoke a string method on it, you will get an error. Java and C++ protect you by forcing you to be upfront and formal about the kind of object each variable is going to refer to.

In one sense Python is representative of a whole class of languages, sometimes referred to as “scripting languages.” Other languages in the same category as Python are Ruby and Perl. Java is representative of what I will call industrial strength languages. Industrial strength languages are good for projects with several people working on the project where being formal and careful about what you do may impact lots of other people. Languages in this category include C++, C, C# and Ada.

Programming languages will always change. As the field of computer science advances there will be new programming languages and you will need to learn them. It is important to learn several programming languages so that you know what to expect. There are certain features that most programming languages have in common; variables, loops, conditionals, functions. And there are some features that are unique. If you know what is common in languages that is a good place to start.

Why Learn Java? Why not C or C++?

  • Java is the most widely taught programming language.
  • Java is more popular
  • Java is industrial strength used for large systems by large groups of people
  • If you know Java learning C++ is easy.

Java is an enormous language. There are over 3700 different classes included in the Java 6 Standard Edition. We could not begin to scratch the surface of these classes even if we devoted all 2700 minutes of class time that we have in a semester. However Java is very powerful and we will write some very powerful programs this semester.

Lets look at a Java Program

A time honored tradition in Computer Science is to write a program called “hello world.” The “hello world” program is simple and easy. There are no logic errors to make, so getting it to run relies only on understanding the syntax. To be clear lets look a a “complicated” version of hello world for Python:

def main():
    print "Hello World!"

Remember that we can define this program right at the Python command line and then run it:

>>> main()
"Hello World!"
>>>

Now lets look at the same program written in Java:

public class Hello {

    public static void main(String[] args) {
        System.out.println("Hello World!");
    }

}

What we see is that at the core there are a few similarities, such as a main and the string “Hello World” However there is a lot more stuff around the edges that make it harder to see the core of the program. Do not worry! An important skill for a computer scientist is to learn what to ignore and what to look at carefully. You will soon find that there are some elements of Java that will fade into the background as you become used to seeing them. One thing that will help you is to learn a little bit about Java Naming Conventions.

The first question you probably have about this little program is “How do I run it?” Running a Java program is not as simple as running a Python program. The first thing you need to do with a Java program is compile it. The first big difference between Java and Python is that Python is an interpreted language. We could run our Python programs in the Python interpreter and we were quite happy to do that. Java makes running programs a two step process. First we must type the hello world program into a file and save that file using the name Hello.java The file name must be the same as the public class you define in the file. Once we have saved the file we compile it from the command line as follows:

$ javac Hello.java
$ ls -l Hello.*
-rw-r--r--   1 bmiller  bmiller  391 Jul 19 17:47 Hello.class
-rw-r--r--   1 bmiller  bmiller  117 Jul 19 17:46 Hello.java

The command javac compiles our java source code into compiled byte code and saves it in a file called Hello.class. Hello.class is a binary file so you won’t learn much if you try to examine the class file with an editor. Hopefully you didn’t make any mistakes, but if you did you may want to consult Common Mistakes for helpful hints on compiler errors.

Now that we have compiled our java source code we can run the compiled code using the java command.

$ java Hello
Hello World!
$

Now you may be wondering what good is that extra step? What does compiling do for us? There are a couple of important benefits we get from compiling:

  • Early detection of errors
  • Faster Program Execution

The job of the compiler is to turn your java code into language that the Java Virtual Machine (JVM) can understand. We call the code that the JVM understands byte code. The JVM interprets the byte code much like the Python interpreter interprets your Python. However since byte code is much closer to the native language of the computer it can run faster.

When the compiler does the translation it can find many different kinds of errors. For example if you make a typo the compiler will find the typo and point it out to you before you ever run the program. We will look at some examples of compiler errors shortly. Chances are you will create some on your own very soon too.

The online server combines the compile and execute steps when you click on Run in the book..

Now that we have run our hello world program, lets go back and look at it carefully to see what we can learn about the Java language. This simple example illustrates a few very important rules:

  1. Every Java program must define a class, all code is inside a class.
  2. Everything in Java must have a type
  3. Every Java program must have a function called public static void main(String[] args) in a file with the name of the class, with ”.java” appended.

Lets take the hello world example a line at a time to see how these rules are applied. On line 1 we see that we are declaring a class called Hello. As rule 1 says all Java code resides inside a class. Unlike Python where a program can simply be a bunch of statements in a file, Java programs must be inside a class. So, we define a class, Hello is not a very useful class it has no instance variables, and only one method. You will also notice the curly brace { In Java blocks of code are identified by pairs of curly braces. The block starts with a { and ends with a }. You will notice that I indented my code that followed the left brace, but in Java this is only done by convention it is not enforced.

On the next line we start our method definition. The name of this method is:

public static void main(String[] args)

Everything on this line is significant, and helps in the identification of this method. For example the following lines look similar but are in fact treated by Java as completely different methods:

  • public void main(String[] args)
  • public static void main(String args)
  • public static void main()
  • void main(String args)

Just digging in to this one line will take us deep into the world of Java, so we are going to start digging but we are not going to dig too deeply right away. Much of what could be revealed by this one line is better understood through other examples, so be patient.

The first word, public indicates to the Java compiler that this is a method that anyone can call. We will see that Java enforces several levels of security on the methods we write, including public, protected, and private methods.

The next word, static tells Java that this is a method that is part of the class, but is not a method for any one instance of the class (like @staticmethod in Python). The kind of methods we typically wrote in Python required an instance in order for the method to be called. With a static method, the object to the left of the . is a class, not an instance of the class. For example the way that we would call the main method directly is: Hello.main(parameter1). For now you can think of static methods the same way you think of methods in Python modules that don’t require an instance, for example the math module contains many methods: sin, cos, etc. You probably evaluated these methods using the names math.cos(90) or math.sin(60).

The next word, void tells the Java compiler that the method main will not return a value. This is roughly analogous to omitting the return statement in a Python method. In other words the method will run to completion and exit but will not return a value that you can use in an assignment statement. As we look at other examples we will see that every Java function must tell the compiler what kind of an object it will return. This is in keeping with the rule that says everything in Java must have a type. In this case we use the special type called void which means no type.

Next we have the proper name for the method: main. The rules for names in Java are similar to the rules in Python. Names can include letters, numbers, and the _. Names in Java must start with a letter.

Finally we have the parameter list for the method. In this example we have one parameter. The name of the parameter is args however, because everything in Java must have a type we also have to tell the compiler that the value of args is an array of strings. For the moment You can just think of an array as being the same thing as a list in Python. The practical benefit of declaring that the method main must accept one parameter and the parameter must be a an array of strings is that if you call main somewhere else in your code and and pass it an array of integers or even a single string, the compiler will flag it as an error.

That is a lot of new material to digest in only a single line of Java. Lets press on and look at the next line: System.out.println("Hello World!");. Python has some global functions, like print. In Java everything is in a class. This line should look a bit more familiar to you. Python and Java both use the dot notation for finding names. In this example we start with System. System is a class. Within the system class we find the object named out. The out object is the standard output stream for this program. Having located the out object Java will now call the method named println(String s) on that object. The println method prints a string and adds a newline character at the end. Anywhere in Python that you used the print function (without a keyword argument for end) you will use the System.out.println method in Java.

Now there is one more character on this line that is significant and that is the ; at the end. In Java the ; signifies the end of a statement. Unlike Python where statements are almost always only one line long java statements can spread across many lines. The compiler knows it has reached the end of a statement when it encounters a ;. This is a very important difference to remember. In Java the following statements are all legal and equivalent. I would not encourage you to write your code like this, but you should know that it is legal.

System.out.println("Hello World");
System.out.println("Hello World")
;
System.out.println
    (
     "Hello World"
    )     ;
System.
  out.
    println("Hello World")
    ;

The last two lines of the hello world program simply close the two blocks. The first or outer block is the class definition. The second or inner block is the function definition.

If we wanted to translate the Java back to Python we would have something like the following class definition.

class Hello(object):
    @staticmethod
    def main(args):
        print "Hello World!"

Notice that we used the decorator @staticmethod to tell the Python interpreter that main is going to be a static method. The impact of this is that we don’t have to, indeed we should not, use self as the first parameter of the main method! Using this definition we can call the main method in a Python session like this:

>>> Hello.main("")
Hello World!
>>>

Java Data Types

Numeric

One of the great things about Python is that all of the basic data types are objects. Integers are objects, floating point numbers are objects, lists are objects, everything. In Java that is not the case. In Java some of the most basic data types like integers and floating point numbers are not objects. The benefit of having these primitive data types be non-objects is that operations on the primitives are fast. The problem is that it became difficult for programmers to combine objects and non-objects in the way that we do in Python. So, eventually all the non-object primitives ended up with Objectified versions.

Primitive Object
int Integer
long Long
float Float
double Double
char Char
boolean Boolean

In older versions of Java it was the programmers responsibility to convert back and forth from a primitive to an object whenever necessary. This processing of converting a primitive to an object was called “boxing.” The reverse process is called “unboxing.” In Java 5, the compiler became smart enough to know when to convert back and forth and is called “autoboxing”, for instance when passing an int to a function expecting an Integer.

Only Boolean (Python bool) and Double (Python float) are direct arithmetic matches. The Boolean literals are slightly different: capitalized in Python, not in Java (true, false).

Because the Java primitive types are stored in fixed-size spaces, as a Python float is, they have restrictions:

long
64 bits; range -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
int
32 bits; range -2,147,483,648 to 2,147,483,647
double
64 bits; maximum magnitude: \(1.7976931348623157(10^{308})\); about 15 digits of accuracy
float
32 bits; maximum magnitude: \(3.402823(10^{38})\); about 7 digits of accuracy

A char is a single character, with literals in single quotes, like ‘x’. In Java this is distinct from a String, even a String with one character, like “x”.

Lets go back in time and look at another of our very early Python programs. Here is a simple Python function to convert a Fahrenheit temperature to Celsius.

def main():
    fahr = float(input("Enter the temperature in F: "))
    cel = (fahr - 32) * 5.0/9.0
    print "the temperature in C is: ", cel

main()

Next, lets look at the Java Equivalent.

import java.util.Scanner;

public class TempConv {
    public static void main(String[] args) {
        double fahr;
        double cel;
        Scanner in;

        in = new Scanner(System.in);
        System.out.println("Enter the temperature in F: ");
        fahr = in.nextDouble();

        cel = (fahr - 32) * 5.0/9.0;
        System.out.println("The temperature in C is: " + cel);
    }

}

There are several new concepts introduced in this example. We will look at them in the following order:

  • Import
  • Variable Declaration
  • Input/Output and the Scanner Class

Import

In Java you can use any class that is available without having to import the class subject to two very important conditions:

  1. The javac and java must know that the class exists.
  2. You must use the full name of the class

You first question might be how do the java and javac commands know that certain classes exist. The answer is the following:

  1. Java knows about all the classes that are defined in .java and .class files in your current working directory.
  2. Java knows about all the classes that are shipped with java.
  3. Java knows about all the classes that are included in your CLASSPATH environment variable. Your CLASSPATH environment variable can name two kinds of structures.
    1. A jar file that contains java classes
    2. Another directory that contains java class files

You can think of the import statement in Java as working a little bit like the from module import xxx statement in Python. However, behind the scenes the two statements actually do very different things. The first important difference to understand is that the class naming system in Java is very hierarchical. The full name of the Scanner class is really java.util.Scanner. You can think of this name as having two parts: The first part java.util is called the package and the last part is the class. We’ll talk more about the class naming system a bit later. The second important difference is that it is the Java class loader’s responsibility to load classes into memory, not the import statement’s.

Note that an import statement appears at the beginning of the code, before any class definition.

So, what exactly does the import statement do? What it does is tell the compiler that we are going to use a shortened version of the class’s name. In this example we are going to use the class java.util.Scanner but we can refer to it as just Scanner. We could use the java.util.Scanner class without any problem and without any import statement provided that we always referred to it by its full name. As an Experiment you may want to try this yourself. Remove the import statement and change the string Scanner to java.util.Scanner in the rest of the code. The program should still compile and run.

Operators

Just as some Java datatypes correspond closely Python, while others have less in common, the same is true of operators.

Addition, subtraction, multiplication and negation correspond closely, except if the result does not fit in the range of the result type, an overflow error occurs, silently in Java!

Java does not use // for any division. (It starts a comment.) Instead / does double duty, based on the type of the operands. If at least one operand is a double, it behaves like Python /. If both are ints, it behaves like Python // when both operands have the same sign, but not when the real number result would be negative: Python returns the floor of the real division (greatest integer <= real result) while Java truncates toward 0.

In the Fahrenheit conversion program, note that the expression (fahr - 32) * 5.0/9.0 evaluates correctly, and would have the same result if written (fahr - 32) * 5/9. Evaluating from left to right starting with double variable fahr means every intermediate operation produces a double result. However, the expression (fahr - 32) * (5/9) would not be the same! The parentheses around 5/9 means this integer quotient is calculated separately. The int operands means it would be the same as Python 5//9, so it equals 0! In Java 5/9 is not anything like 5/9 in math.

The remainder operation % is the same in both languages if the sign of both operands is the same, but not with different signs. With integers d and n, d*(n//d) + n%d always equals n in Python, and d*(n/d) + n%d always equals n in Java, but since integer division works differently with different signs, so does %.

Boolean operator and, or and not in Python correspond exactly to Java &&, || and ! if operands have Boolean values.

Declaring Variables

Here is where we run into one of the most important differences between Java and Python. Python is a dynamically typed language. In a dynamically typed language a variable can refer to any kind of object at any time. When the variable is used, the interpreter figures out what kind of object it is. Java is a statically typed language. In a statically typed language the association between a variable and the type of object the variable can refer to is determined when the variable is declared. Once the declaration is made it is an error for a variable to refer to an object of any other type.

In the example above, lines 5—7 contain variable declarations. Specifically we are saying that fahr and cel are going to reference objects that are of type double. The variable in will reference a Scanner object. This means that if we were to try an assignment like fahr = "xyz" the compiler would generate an error because "xyz" is a string and fahr is supposed to be a double. ` When creating a new object, like a new Scanner, Java requires an explicit new before the type being constructed.

For Python programmers the following error is likely to be even more common. Suppose we forgot the declaration for cel and instead left line 6 blank. What would happen when we type javac TempConv.java on the command line?

TempConv.java:13: cannot find symbol
symbol  : variable cel
location: class TempConv
         cel = (fahr - 32) * 5.0/9.0;
         ^
TempConv.java:14: cannot find symbol
symbol  : variable cel
location: class TempConv
         System.out.println("The temperature in C is: " + cel);
                                                          ^
2 errors

When you see the first kind of error, where the symbol is on the left side of the equals sign it usually means that you have not declared the variable. If you have ever tried to use a Python variable that you have not initialized the second error message will be familiar to you. The difference here is that we see the message before we ever try to test our program. More common error messages are discussed in the section Common Mistakes.

The general rule in Java is that you must decide what kind of an object your variable is going to reference and then you must declare that variable before you use it. There is much more to say about the static typing of Java but for now this is enough.

Input / Output / Scanner

In the previous section you saw that we created a Scanner object. In Java Scanner objects make getting input from the user, a file, or even over the network relatively easy. In our case we simply want to ask the user to type in a number at the command line, so in line 9 we construct a Scanner by calling the constructor and passing it the System.in object. Notice that this Scanner object is assigned to the name in, which we declared to be a Scanner on line 7. System.in is similar to System.out except of course it is used for input. If you are wondering why we must create a Scanner to read data from System.in when we can write data directly to System.out using println, you are not alone. We will talk about the reasons why this is so later when we talk in depth about Java streams. You will also see in other examples that we can create a Scanner by passing the Scanner a File object. You can think of a scanner as a kind of “adapter” that makes low level objects easier to use.

On line 11 we use the Scanner object to read in a number. Here again we see the implications of Java being a strongly typed language. Notice that we must call the method nextDouble. Because the variable fahr was declared as a double. So, we must have a function that is guaranteed to return each kind of object we might want to read. In this case we need to read a double so we call the function nextDouble. The compiler matches up these assignment statements and if you try to assign the results of a method call to the wrong kind of variable it will be flagged as an error.

Table 2 shows you some commonly used methods of the scanner class. There are many more methods supported by this class and we will talk about how to find them in the next chapter.

Return type Method name Description
boolean hasNext() returns true if more data is present
boolean hasNextInt() returns true if the next thing to read is an integer
boolean hasNextFloat() returns true if the next thing to read is a float
boolean hasNextDouble() returns true if the next thing to read is a double
Integer nextInt() returns the next thing to read as an integer
Float nextFloat() returns the next thing to read as a float
double nextDouble() returns the next thing to read as a double
String next() returns the next thing to read as a String
String nextLine() returns all before the next newline

The last option is like what is returned by Python input. While in Python you might use input, and then split the line, and convert individual pieces to the types you want, the other Java methods do not read a whole line. They just skip white space until the next token, possibly past a newline, to process a single token, but they do not process any newline after the token they process.

In particular, if there is token at the end of a line that is read directly, then an immediately following nextLine() would return the empty String.

Of course Java is more well known for producing applications that have more of a user interface to them than reading and writing from the command line. Lets look at a version of our temperature control application that uses dialog boxes for input and output.

import javax.swing.*;

public class TempConvGUI {

    public static void main(String[] args) {
        String fahrString;
        double fahr, cel;

        fahrString = JOptionPane.showInputDialog("Enter the temperature in F");
        fahr = new Double(fahrString);
        cel = (fahr - 32) * 5.0/9.0;

        JOptionPane.showMessageDialog(null,"The temperature in C is, " + cel);
    }

}

This example illustrates a couple of interesting points:

First, the function call JOptionPane.showInputDialog pops up a dialog box to allow you to enter a temperature. But, since you could enter anything into the text input box it returns a String. On the next line the string is converted into a Double by the Double constructor. This is similar to what happens in Python when you call float() with either a string or an integer as the argument.

The next dialog box is JOptionPane.showMessageDialog. Notice that the first parameter is null In Java null serves the same purpose as None in Python. The first parameter is null because we do not have a ‘main window’ for this little application. When we look at creating full blown java programs with user interfaces, we will learn more about this parameter.

The second parameter is "The temperature in C is, " + cel. Now you may be thinking to yourself that this must surely be a violation of the strong typing I have been describing to you. After all you should not be able to add together a string and a Double right? You are correct, however, all Java objects have a method called toString. The toString method acts much like the Python method __str__() and is called automatically by the compiler whenever it makes sense to convert a Java object to a string.

String

Strings in Java and Python are quite similar. Like Python, Java strings are immutable. However, manipulating strings in Java is not quite as obvious since Strings do not support an indexing or slicing operator. That is not to say that you can’t index into a Java string, you can. You can also pull out a substring just as you can with slicing. The difference is that Java uses method calls where Python uses Operators.

In fact this is the first example of another big difference between Java and Python. Java does not support any operator overloading. Table 3 maps common Python string operations to their Java counterparts. For the examples shown in the table we will use a string variable called “str”

Python Java Description
str[3] str.charAt(3) Return character in 3rd position
str[2:4] str.substring(2,4) Return substring from 2nd up to but not including 4th
len(str) str.length() Return the length of the string
str.find('x') str.indexOf("x") Find the first occurrence of x
str.split(',') str.split(",") Split the string at ',' into a list/array of strings
str + str str.concat(str) Concatenate two strings together (can still use +)
str.strip() str.trim() Remove any whitespace at the beginning or end
str.replace("me", "I") same as Python Replace all occurrences of first parameter by second

The example for split was chosen carefully. For Java there must be a parameter, and it is a regular expressions, or regex for short: another specialized language with variants embedded in many programming languages. To match the behavior of a Python string parameter, treated verbatim, you need to escape any of the following characters you use: []{}()+*^$?

The regex escape character is \, but unfortunately that is also the string escape character, so to get it to the regex, it, too, needs to be escaped. So if you wanted to split on +, you would need str.split("\\+").

The analog for str.split() (no Python parameter) is trickier. Python ignores white space on either end, and splits out tokens with any number of whitespace characters in between. Java will generate empty strings in the list if there is whitespace at the beginning, and you need a regex to handle multiple whitespace characters together, so to match Python you need the mouthful str.trim().split("\\s+").

List

Lets look at another early Python program. We are going to read numbers from a file and produce a histogram that shows the frequency of the various numbers. The data file we will use has one number between 0 and 9 on each line of the file. Here is a simple Python program that creates and prints a histogram.

def main():
    count = [0]*10
    data = open('test.dat')

    for line in data:
        count[int(line)] = count[int(line)] + 1

    idx = 0
    for num in count:
        print(idx, " occured ", num, " times.")
        idx += 1

Now if we run this program on a data file that looks like this:

9 8 4 5 3 5 2 1 5

We will get output that looks like this:

0 occurred 0 times
1 occurred 1 times
2 occurred 1 times
3 occurred 1 times
4 occurred 1 times
5 occurred 3 times
6 occurred 0 times
7 occurred 0 times
8 occurred 1 times
9 occurred 1 times

Lets review what is happening in this little program. In the first line we create a list and initialize the first 10 positions in the list to be 0. Next we open the data file called ‘test.dat’ Third, we have a loop that reads each line of the file. As we read each line we convert it to an integer and increment the counter at the position in the list indicated by the number on the line we just read. Finally we iterate over each element in the list printing out both the position in the list and the total value stored in that position.

To write the Java version of this program we will have to introduce several new Java concepts. First, you will see the Java equivalent of a list, called an ArrayList. Next you will see three different kinds of loops used in Java. Two of the loops we will use are going to be very familiar, the third one is different from what you are used to in Python but is easy when you understand the syntax:

while
Used with boolean expression for loop exit condition.
for
Used to iterate over a sequence. This is very similar to for i in xxx where xxx is a list or string or file.
for
Used to iterate through a sequence of numbers. This is most similar to for i in range(), except the syntax is different.

Here is the Java code needed to write the exact same program:

import java.util.Scanner;
import java.util.ArrayList;
import java.io.File;
import java.io.IOException;

public class Histo {

    public static void main(String[] args) {
        Scanner data = null;
        ArrayList<Integer> count;
        int idx;

        try {
                data = new Scanner(new File("test.dat"));
        }
        catch ( IOException e) {
            System.out.println("Sorry but I was unable to open your data file");
            e.printStackTrace();
            System.exit(0);
        }

        count = new ArrayList<Integer>();
        for (int i =0; i<10; i++) {
            count.add(0);
        }

        while(data.hasNextInt()) {
            idx = data.nextInt();
            count.set(idx,count.get(idx)+1);
        }

        idx = 0;
        for(int i : count) {
            System.out.println(idx + " occurred " + i + " times.");
            idx++;
        }
    }
}

Example test.dat:

1 2 3
4 5
6
7
8 9 1 2 3
4
5

Before going any further, I suggest you try to compile the above program and run it on some test data that you create.

Now, lets look at what is happening in the Java source. As usual we declare the variables we are going to use at the beginning of the method. In this example we are declaring a Scanner variable called data, an int called idx and an ArrayList called count. However, there is a new twist to the ArrayList declaration. Unlike Python where lists can contain just about anything, in Java we let the compiler know what kind of objects our array list is going to contain. In this case the ArrayList will contain Integers. The syntax we use to declare what kind of object the list will contain is the <Type> syntax.

Technically, you don’t have to declare what is going to be on an array list. The compiler will allow you to leave the <``*Type*>`` off the declaration. If you don’t tell Java what kind of object is going to be on the list Java will give you a warning message like this:

Note: Histo.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

Without the <Integer> part of the declaration Java simply assumes that any object can be on the list. However, without resorting to an ugly notation called casting, you cannot do anything with the objects on a list like this! So, if you forget you will surely see more errors later in your code. (Try it and see what you get)

Lines 13—20 are required to open the file. Why so many lines to open a file in Java? The additional code mainly comes form the fact that Java forces you to reckon with the possibility that the file you want to open is not going to be there. If you attempt to open a file that is not there you will get an error. A try/catch construct allows us to try things that are risky, and gracefully recover from an error if one occurs. The following example shows the general structure of a try catch block.

try {
   Put some risky code in here.... like opening a file
}
catch (Exception e) {
   If an error happens in the try block an exception is thrown.
   We will catch that exception here!
}

Notice that in line 16 we are catching an IOException. In fact we will see later that we can have multiple catch blocks to catch different types of exceptions. If we want to be lazy and catch any old exception we can catch an Exception which is the parent of all exceptions.

The statement e.printStackTrace(); prints a stack trace something like] when Python bombs out. The statement System.exit(0); terminates the program, no matter how deep the stack is. The 0 is a value returned to the operating system. A 0 usually means success. A different value might make sense here.

On line 22 we create our array list. It will grow or shrink dynamically as needed just like a list in Python.

On line 23 we start the first of three loops. The for loop on lines 23–25 serves the same purpose as the Python statement count = [0]*10. Java does not have the Python multiplication operator for lists. Instead it adds 0 to the empty list (appends in Python), 10 times.

The syntax of this for loop probably looks very strange to you, but in fact it is not too different from what happens in Python using range. In fact for(int i = 0; i < 10; i++) is exactly equivalent to the Python for i in range(10) The first statement inside the parenthesis declares and initializes a loop variable i. The second statement is a Boolean expression that is our exit condition. In other words we will keep looping as long as this expression evaluates to true. The third clause is used to increment the value of the loop variable at the end of iteration through the loop. In fact i++ is Java shorthand for i = i + 1 or i += 1 that also exist in Python. Java also supports the shorthand i-- to decrement the value of i. Try to rewrite the following Python for loops as Java for loops:

  • for i in range(2,101,2)
  • for i in range(1,100)
  • for i in range(100,0,-1)
  • for x,y in zip(range(10),range(0,20,2)) [hint, you can separate statements in the same clause with a ,]

Note that zip creates a sequence of tuples or corresponding elements of multiple sequences. zip(range(10),range(0,20,2))) produces the sequence (0, 0), (1, 2), (2, 4), (3, 6), (4, 8), (5, 10), (6, 12), (7, 14), (8, 16), (9, 18).

The next loop (lines 27–30) shows a typical Java pattern for reading data from a file. Java while loops and Python while loops are identical in their logic. In this case we will continue to process the body of the loop as long as data.hasNextInt() returns true. This is a well-named method: true if there is a next token and if that token can be interpreted as an int. There are corresponding methods for the other types a Scanner can read.

Line 29 illustrates another important difference between Python and Java. Notice that in Java we can not write count[idx] = count[idx] + 1. This is because in Java there is no overloading of operators. Square brackets are used only for accessing array elements by index. Everything except the most basic math and logical operations is done using methods. So, to set the value of an ArrayList element we use the set method. The first parameter of set indicates the index or position in the ArrayList we are going to change. The next parameter is the value we want to set. Notice that once again we cannot use the indexing square bracket operator to retrieve a value from the list, but we must use the get method.

The last loop in this example is similar to the Python for loop where the object of the loop is a Sequence. Note that the introduction of a new variable i here, requires a type for i to be declared.

To distinguish this form of for statement fromt he earlier one, it is sometimes called a for-each statement. In Java we can use this kind of for loop over all kinds of sequences, which are called Collection classes in Java. The for loop on line 33 for(int i : count) is equivalent to the Python loop for i in count: This loop iterates over all of the elements in the ArrayList called count. Each time through the loop the Integer variable i is bound to the next element of the ArrayList. If you tried the experiment of removing the <Integer> part of the ArrayList declaration you probably noticed that you had an error on this line. Why?

List methods: Suppose w and w2 are lists of strings –

Python Java notes
value of w[3] w.get(3) Return item in 3rd position
w[3] = 'a' w.set(3, "a") Set item in 3rd position
w.append('a') w.add("a") Append item
len(w) w.size() Return the number of items in the list
w.find('x') w.indexOf("x") Find index of the first occurrence of x, or -1
w += w2 w.addAll(w2) add all of list w2, modifying w
'x' in w w.contains("x") membership test
not bool(w) w.isEmpty() Python w is True if not empty
w[:]=[] w.clear() Remove all items
del(w[2]) w.remove(2) remove item at position 2
w.sort() w.sort() sort

If you want to iterate over w[2:4] and not change anything in the list, w.subList(2,4) is an analog, but it does not create a new list: it is a view of w. The analog of w2 = w[2:4], which copies item references to a new list, is w2 = new ArrayList<String>(w.subList(2,4)).

Joining is a string method that uses string lists. To separate with ", " the strings in list sList:

', '.join(sList) in Python
String.join(", ", sList) in Java

In Java sList can be a more general iterable sequence of Strings, including an array of Strings.

For more extensive use of Java ArrayLists, you will want to see its documentation, https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html.

Arrays

As I said at the outset of this Section we are going to use Java ArrayLists because they are easier to use and more closely match the way that Python lists behave. However, if you look at Java code on the internet or even in your Core Java books you are going to see examples of something called arrays. In fact you have already seen one example of an array declared in the ‘Hello World’ program. Lets rewrite this program to use primitive arrays rather than array lists.

import java.util.Scanner;
import java.io.File;
import java.io.IOException;

public class HistoArray {
    public static void main(String[] args) {
        Scanner data = null;
        int[] count = new int[10];
        int idx;


        try {
                data = new Scanner(new File("test.dat"));
        }
        catch ( IOException e) {
            System.out.println("Sorry but I was unable to open your data file");
            e.printStackTrace();
            System.exit(0);
        }

        while(data.hasNextInt()) {
            idx = data.nextInt();
            count[idx] = count[idx] + 1;
        }

        idx = 0;
        for(int i : count) {
            System.out.println(idx + " occurred " + i + " times.");
            idx++;
        }
    }
}

The main difference between this example and the previous example is that we declare count to be an array of integers. The length must be specified when the new object is created. In general when instantiating a new object the new must be included.

Java objects are always intialized when created. Numeric arrays are initialized to all 0’s, which is what we want here.

Then notice that on line 24 we can use the square bracket notation to index into an array.

We also can initialize short arrays directly using a sequence of values in braces. The array of 10 0’s could have been initialized

int[] count = {0,0,0,0,0,0,0,0,0,0};

Automatic initialization for other element types: new boolean[5] sets all 5 elements to false. Any array of objects gets all item values null (no object, similar to Python None) by default.

Arrays have a length attribute – accessed with no parentheses after it. The length of array a is a.length. This is unlike Strings! The length of String s is s.length(). Both cannot be changed for a given object, array or string.

It is also easy to conflate syntax for list lengths. Lists can be changed: A different method name is used for the (current) length of ArrayList al: al.size().

Here is a function to return a new array containing the squares of the numbers in a passed array. See the use of the length attribute:

public static int[] squares(int[] nums)
{
    int n = nums.length;
    int[] sq = new int[n];
    for (int i = 0; i < n; i++) {
        sq[i] = nums[i]*nums[i];
    }
    return sq;
}

Dictionary

Just as Python provides the dictionary when we want to have easy access to key, value pairs, Java also provides us a similar mechanism. Rather than the dictionary terminology, Java calls these objects Maps. Java provides two different implementations of a map, one is called the TreeMap and the other is called a HashMap. As you might guess the TreeMap uses a balanced binary tree behind the scenes, and the HashMap uses a hash table.

Lets stay with a simple frequency counting example, only this time we will count the frequency of words in a document. A simple Python program for this job could look like this:

def main():
    data = open('alice30.txt')
    wordList = data.read().split()
    count = {}
    for w in wordList:
        w = w.lower()
        count[w] = count.get(w,0) + 1

    keyList = sorted(count.keys())
    for k in keyList:
        print('{:20} occurred {:4} times'.format(k, count[k]))

main()

Here is alice30.txt:

Down, down, down.  Would the fall NEVER come to an end!  'I
wonder how many miles I've fallen by this time?' she said aloud.
'I must be getting somewhere near the centre of the earth.  Let
me see:  that would be four thousand miles down, I think--' (for,
you see, Alice had learnt several things of this sort in her
lessons in the schoolroom, and though this was not a VERY good
opportunity for showing off her knowledge, as there was no one to
listen to her, still it was good practice to say it over) '--yes,
that's about the right distance--but then I wonder what Latitude
or Longitude I've got to?'  (Alice had no idea what Latitude was,
or Longitude either, but thought they were nice grand words to
say.)

Notice that the structure of the program is very similar to the numeric histogram program.

import java.util.Scanner;
import java.util.ArrayList;
import java.io.File;
import java.io.IOException;
import java.util.TreeMap;

public class HistoMap {

    public static void main(String[] args) {
        Scanner data = null;
        TreeMap<String,Integer> count;
        int idx;
        String word;
        int wordCount;

        try {
                data = new Scanner(new File("alice30.txt"));
        }
        catch ( IOException e) {
            System.out.println("Sorry but I was unable to open your data file");
            e.printStackTrace();
            System.exit(0);
        }

        count = new TreeMap<String,Integer>();

        while(data.hasNext()) {
            word = data.next().toLowerCase();
            wordCount = count.get(word);
            if (wordCount == null) {
                wordCount = 0;
            }
            count.put(word,++wordCount);
        }

        for(String i : count.keySet()) {
            System.out.format("%-20s occurred %5d times\n", i, count.get(i) );
        }
    }
}

The TreeMap has both keys and values, so two types are specified TreeMap<keyType, valueType>. This needs to occur both when the type is declared and later where the constructor is used.

The method hasNext with no type appended to the name is checking if there is any token next.

Python also has a get method for dictionaries, but the usage when there is no element is different: Python causes an error unless a default value is included as a second parameter. Java does not trigger an error, but just returns null (no object, used something like Python’s None).

Here there is a ++ operator, but the ++ is before the variable ++wordCount. If ++wordCount were used as a separate statement, it would be equivalent to wordCount++, but here is is an expression whose value is used in a larger statement: With the ++ first, the value of the expression is the new value, after incrementing. With the ++ second, the value of the expression is the original value, before incrementing. To avoid these fine points, you are encouraged not to use either where the expression value is used immediately. Same idea for the decrement operator --.

The method System.out.format is used much like printing a string with the format method. The % followed by modifiers is used much like the {} in Python format strings. This is more important in Java, since System.out.println and System.out.print can only take a single argument, unlike the allowed list in Python for print. Java format modifiers end with a type designation: s prints anything as a string, d is for ints. A field width can be given after the %, with a negative number indicating left justification. Though not appearing in the example, f is the type identifier for double. You can show field width and precision for a double:

double x = 2.345;
System.out.format("Two decimal places:%7.2, rounded");
// prints would print: Two decimal places:   2.35, rounded
// see the 7 character field between the literal colon and comma

The Java getKeys() method is like the Python keys() method. The extra step to sort the keys in Python is needed since a dict corresponds to a Java HashMap, generating keys in an unpredictable order. This Java example used a Treemap, which has slower access, but maintains the keys in order.

Improve the program above to remove the punctuation.

Conditionals

Conditional statements in Python and Java are very similar. In Python we have three patterns:

Simple if

if condition:
    statement1
    statement2
    ...

In Java this same pattern is simply written as:

if (condition) {
    statement1
    statement2
    ...
}

Once again you can see that in Java the curly braces define a block rather than indentation. In Java the parenthesis around the condition are required, but there is no colon.

if else

if condition:
    statement1
    statement2
    ...
else:
    statement1
    statement2
    ...

In Java this is written as:

if (condition) {
    statement1
    statement2
    ...
} else {
    statement1
    statement2
    ...
}

elif

Java does not have an elif pattern like Python. In Java you can get the functionality of an elif statement by nesting if and else. Here is a simple example in both Python and Java.

grade = int(input('enter a grade'))
if grade < 60:
    print 'F'
elif grade < 70:
    print 'D'
elif grade < 80:
    print 'C'
elif grade < 90:
    print 'B'
else:
    print 'A'

In Java we have a couple of ways to write this

public class ElseIf {
    public static void main(String args[]) {
     int grade = 85;

     if (grade < 60) {
         System.out.println('F');
     } else {
         if (grade < 70) {
             System.out.println('F');
         } else {
             if (grade < 80) {
                 System.out.println('F');
             } else {
                 if (grade < 90) {
                     System.out.println('F');
                 } else {
                     System.out.println('F');
                 }
             }
         }
     }
   }
 }

We can get even closer to the elif statement by taking advantage of the Java rule that a single statement does not need to be enclosed in curly braces. Since the if is the only statement used in each else we can get away with the following.

public class ElseIf {
    public static void main(String args[]) {
     int grade = 85;
     if (grade < 60) {
         System.out.println('F');
     } else if (grade < 70) {
         System.out.println('D');
     } else if (grade < 80) {
         System.out.println('C');
     } else if (grade < 90) {
         System.out.println('B');
     } else  System.out.println('A');
    }
}

switch

Java also supports a switch statement that acts something like the elif statement of Python under certain conditions. To write the grade program using a switch statement we would use the following:

public class SwitchUp {
    public static void main(String args[]) {
     int grade = 85;

     int tempgrade = grade / 10;
     switch(tempgrade) {
     case 10:
     case 9:
         System.out.println('A');
         break;
     case 8:
         System.out.println('B');
         break;
     case 7:
         System.out.println('C');
         break;
     case 6:
         System.out.println('A');
         break;
     default:
         System.out.println('F');
     }
   }
 }

The switch statement is not used very often, and I recommend you do not use it! First, it is not as powerful as the else if model because the switch variable can only be compared for equality with an integer or enumerated constant. Second it is very easy to forget to put in the break statement. If the break statement is left out then then the next alternative will be automatically executed. For example if the grade was 95 and the break was omitted from the case 9: alternative then the program would print out both A and B.

Boolean Operators

The conditionals used in the if statement can be boolean variables, simple comparisons, and compound boolean expressions.

Java also supports the boolean expression. condition ? trueValue : falseValue This expression can be used to test a condition as part of an assignment statement. For example a = a % 2 == 0 ? a*a : 3*x -1 In the previous assignment statement the expression a%2 ==0 is first checked. If it is true then a is assigned the value a * a if it is false then a is assigned the value of 3*x-1. Of course all of this could have been accomplished using a regular if else statement, but sometimes the convenience of a single statement is too much to resist.

Python recently added a corresponding construction (in English!):

trueValue if condition else falseValue

Loops and Iteration

You have already seen a couple of examples of iteration and looping in Java. So this section will just serve as a reference for the differences in Syntax.

Definite Loop

In Python the easiest way to write a definite loop is using the for loop in conjunction with the range function. For example:

for i in range(10):
   print(i)

In Java we would write this as:

for (int i = 0; i < 10; i++ ) {
    System.out.println(i);
}

Recall that the range function provides you with a wide variety of options for controlling the value of the loop variable.

range(stop)
range(start,stop)
range(start,stop,step)

The Java for loop is really analogous to the last option giving you explicit control over the starting, stopping, and stepping in the three clauses inside the parenthesis. You can think of it this way:

for (start clause; stop clause; step clause) {
    statement1
    statement2
    ...
}

If you want to start at 100, stop at 0 and count backward by 5 the Python loop would be written as:

for i in range(100,-1,-5):
    print(i)

In Java we would write this as:

for (int i = 100; i >= 0; i -= 5)
    System.out.println(i);

In Python the for loop can also iterate over any sequence such as a list, a string, or a tuple. Java also provides a variation of its for loop that provides the same functionality in its so called for each loop.

In Python we can iterate over a list as follows:

l = [1, 1, 2, 3, 5, 8, 13, 21]
for fib in l:
   print(fib)

In Java we can iterate over an ArrayList of integers too:

ArrayList<Integer> l = new ArrayList<Integer>();
l.add(1); l.add(1); l.add(2); l.add(3);
for (int i : l) {
    System.out.println(i)
}

This example stretches the imagination a bit, and in fact points out one area where Java’ s primitive arrays are easier to use than an array list. In fact all primitive arrays can be used in a for each loop.

int l[] = {1,1,2,3,5,8,13,21};
for(int i : l) {
    System.out.println(i);
}

To iterate over the characters in a string in Java do the following:

String t = "Hello World";
for (char c : t.toCharArray()) {
    System.out.println(c);
}

Indefinite Loops

Both Python and Java support the while loop. Recall that in Python the while loop is written as:

while  condition:
   statement1
   statement2
   ...

In Java we add parenthesis, drop the colon, and add curly braces to get:

while (condition) {
    statement1
    statement2
    ...
}

Java adds an additional, if seldom used variation of the while loop called the do-loop. The do loop is very similar to while except that the condition is evaluated at the end of the loop rather than the beginning. This ensures that a loop will be executed at least one time. Some programmers prefer this loop in some situations because it avoids an additional assignment prior to the loop. For example:

do {
    statement1
    statement2
    ...
} while (condition);

This is one place in Java where their is a semicolon after a condition!

Loop and Array Exercises

  1. Write a static void method printNums with int array parameter that prints out all the elements of the parameter on one line, blank separated. Use it for tests of methods below.

  2. Write a program including a static void method called noNeg to mutate its int array parameter so all negative elements become 0. Include a test in main. Remember the easy way to initialize an array with braces.

  3. Write a program including a static method called allUpper with a String array parameter that does not modifiy the parameter array, but returns a String array with all the strings of the parameter array in upper case. (Use string method toUpper: "Hello".toUpper() returns "HELLO".) Include a test in main. Use a for-each loop to display the contents fo the returned array.

  4. Write a program including a static boolean method called isIncreasing with an int array parameter that returns true if the array elements are strictly increasing, and false otherwise. Include thorough tests in main.

  5. Write the body of

    public static int factorial(int n)
    

    where factorial(n), or n! in math, means 1*2*...*n for positive n. By convention 0! is 1. 1! is 1; 2! is 1 * 2 = 2; 3! is 1*2*3 = 6.

    What is the largest value of n for which you get the correct answer in Java? (There is no limit until you run out of system memory in Python.)

  6. Same as the last problem by with int type everywhere replaced by long.

Defining Classes in Java

You have already seen how to define classes in Java. Its unavoidable for even the simplest of programs. In this section we will look at how we define classes to create our own data types. Lets start by creating a fraction class to extend the set of numeric data types provided by our language. The requirements for this new data type are as follows:

Here is a mostly complete implementation of a Fraction class in Python 2 that we will refer to throughout this section:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class Fraction:  # Python 2 in __cmp__ and print

    def __init__(self,top,bottom):

        self.num = top        #the numerator is on top
        self.den = bottom     #the denominator is on the bottom


    def __repr__(self):
        if self.num > self.den:
            retWhole = self.num / self.den
            retNum = self.num - (retWhole * self.den)
            return str(retWhole) + " " + str(retNum)+"/"+str(self.den)
        else:
            return str(self.num)+"/"+str(self.den)

    def show(self):
        print self.num,"/",self.den

    def __add__(self,otherfraction):
        # convert to a fraction
        otherfraction = self.toFract(otherfraction)

        newnum = self.num*otherfraction.den + self.den*otherfraction.num
        newden = self.den * otherfraction.den

        common = gcd(newnum,newden)

        return Fraction(newnum/common,newden/common)

    def __radd__(self,leftNum):
        otherfraction = self.toFract(leftNum)            
        newnum = self.num*otherfraction.den + self.den*otherfraction.num
        newden = self.den * otherfraction.den

        common = gcd(newnum,newden)

        return Fraction(newnum/common,newden/common)

    def __cmp__(self,otherfraction): #Python 2 only

        num1 = self.num*otherfraction.den 
        num2 = self.den*otherfraction.num

        if num1 < num2:
           return -1
        else:
           if num1 == num2:
              return 0
           else:
              return 1

    def toFract(self,n):
        if isinstance(n,int):
            otherfraction = Fraction(n,1)
        elif isinstance(n, float):
            wholePart = int(n)
            fracPart = n - wholePart
            # convert to 100ths???
            fracNum = int(fracPart * 100)
            newNum = wholePart * 100 + fracNum
            otherfraction = Fraction(newNum,100)
        elif isinstance(n,Fraction):
            otherfraction = n
        else:
            print "Error: cannot add a fraction to a ", type(n)
            return None
        return otherfraction

#gcd is a helper function for Fraction

def gcd(m,n):
    while m%n != 0:
        oldm = m
        oldn = n

        m = oldn
        n = oldm%oldn

    return n

The instance variables (data members) we will need for our fraction class are the numerator and denominator. Of course in Python we can add instance variables to a class at any time by simply assigning a value to objectReference.variableName In Java all data members must be declared up front.

The declarations of instance variables can come at the beginning of the class definition or the end. Cay Horstman, Author of the Core Java books puts the declarations at the end of the class. I like them at the very beginning so you see the variables that are declared before you begin looking at the code that uses them. With that in mind the first part of the Fraction class definition is as follows:

public class Fraction {
    private int num;  // numerator
    private int den;  // denominator


}

Notice that we have declared num and den to be private. This means that the compiler will generate an error if another method tries to write code like the following:

Fraction f = new Fraction(1,2);
int y = f.num * 10;

Direct access to private instance variables is not allowed. Therefore if we legitimately want to be able to access information such as the numerator or denominator for a particular fraction we must have getter functions. It is very common programming practice to provide getter and setter functions for instance variables in Java.

public int getNumerator() {
    return num;
}

public void setNumerator(int numerator) {
    num = numerator;
}

public int getDenominator() {
    return den;
}

public void setDenominator(int denominator) {
    den = denominator;
}

There are a couple of important things to notice here. First, you will notice that the methods do not have a self parameter. You will also notice that we can simply refer to the instance variables by name without the self prefix, because they have already been declared. This allows the Java compiler to do the work of dereferencing the current Java object. Java does provide a special variable called this that works like the self variable. In Java, this is typically only used when it is needed to differentiate between a parameter or local variable and an instance variable. (It always checks for local variables first.) For example this alternate definition of the the setNumerator method uses this to differentiate between parameters and instance variables.

public void setNumerator(int num) {
    this.num = num;
}

Writing a constructor

Once you have identified the instance variables for you class the next thing to consider is the constructor. In Java, constructors have the same name as the class and are declared public. They are declared without a return type. So any function that is named the same as the class and has no return type is a constructor. Our constructor will take two parameters for the numerator and the denominator.

public Fraction(int num, int den) {
    this.num = num;
    this.den = den;
}

..index:: operator overloading excluded

Methods or Member Functions

Now we come to one of the major differences between Java and Python. The Python class definition used the special methods for addition, and comparison that have the effect of redefining how the standard operators behave. In Java there is no operator overloading. So we will have to write member functions, not operator symbols, to do addition, subtraction, multiplication, and division. Lets begin with addition.

public Fraction add(Fraction otherFrac) {
    int newNum, newDen, common;

    newNum = otherFrac.getDenominator()*this.num +
                             this.den*otherFrac.getNumerator();
    newDen = this.den * otherFrac.getDenominator();
    common = gcd(newNum,newDen);
    return new Fraction(newNum/common, newDen/common );
}

First you will notice that the add member function is declared as public Fraction The public part means that any other method may call the add method. The Fraction part means that add will return a fraction as its result.

Second, you will notice that on line two all of the local variables used in the function are declared. In this case there are three local variables: newNum, newDen, and common. It is a good idea for you to get in the habit of declaring your local variables at the beginning of your function. This declaration section provides a simple road map for the function in terms of the data that will be used. The listing above also makes use of the this variable, you may find it useful to use this until you are comfortable with abandoning your Pythonic ideas about self. Using the this makes a symmetry in referencing the instance variables for the current object, this, and otherFrac, but this. is not actually needed anywhere in this method.

Declaring your variables at the top is not a requirement, it is just a recommended practice for you. Java only requires that you declare your variables before they are used. The following version of Fraction is also legal Java, but may be somewhat less readable.

public Fraction add(Fraction otherFrac) {
    int newNum = otherFrac.getDenominator()*num +
                             den*otherFrac.getNumerator();
    int newDen = den * otherFrac.getDenominator();
    int common = gcd(newNum,newDen);
    return new Fraction(newNum/common, newDen/common );
}

The addition takes place by multiplying each numerator by the opposite denominator before adding. This procedure ensures that we are adding two fractions with common denominators. Using this approach the denominator is computed by multiplying the two denominators. The greatest common divisor function is used to find a common divisor to simplify the numerator and denominator in the result.

Finally on line 8 a new fraction is returned as the result of the computation. The value that is returned by the return statement must match the value that is specified as part of the declaration. So, in this case the return value on line 8 must match the declared value on line 1.

Method Signatures and Overloading

Our specification for this project said that we need to be able to add a Fraction to an int. In Python we can do this by checking the type of the parameter using the isinstance function at runtime. Recall that isinstance(1,int) returns True to indicate that 1 is indeed an instance of the int class. See lines 22 and 53—68 of the Python version of the Fraction class to see how our Python implementation fulfills this requirement.

In Java we can do runtime type checking, but the compiler will not allow us to pass an int to the add function since the parameter has been declared to be a Fraction. The way that we solve this problem is by writing another add method with a different set of parameters. This is called method overloading, legal and common in Java, while there is no analog in Python. Python offsets it by not being statically typed, and allowing optional and keyword parameters.

This idea of overloading raises a very important difference between Python and Java. In Python a method is known by its name only. In Java a method is known by its signature. The signature of a method includes its name, and the types of all of its parameters. The name and the types of the parameters are enough information for the Java compiler to decide which method to call at runtime, even when two methods have the same name.

To solve the problem of adding an int and a Fraction in Java we will overload both the constructor and the add function. We will overload the constructor so that if it only receives a single int it will convert the int into a Fraction. We will also overload the add method so that if it receives an int as a parameter it first construct a Fraction from that integer and then add the two Fractions together. The new methods that accomplish this task are as follows:

public Fraction(int num) {
    this.num = num;
    this.den = 1;
}

public Fraction add(int other) {
    return add(new Fraction(other));
}

Notice that the overloading approach can provide us with a certain elegance to our code. Rather than utilizing if statements to check the types of parameters we just overload functions ahead of time which allows us to call the method we want and allow the compiler to make the decisions for us. This way of thinking about programming takes some practice.

Our full Fraction class to this point would look like the following. You may want to try to compile and run the short test program provided just to see what happens.

public class Fraction {

    private int num;
    private int den;

    public Fraction(int num, int den) {
        this.num = num;
        this.den = den;
    }

    public Fraction(int num) {
        this.num = num;
        this.den = 1;
    }

    public Fraction add(Fraction other) {
        int newNum, newDen, common;

        newNum = other.getDenominator()*this.num +
                 this.den*other.getNumerator();
        newDen = this.den * other.getDenominator();
        common = gcd(newNum,newDen);
        return new Fraction(newNum/common, newDen/common );
    }

    public Fraction add(int other) {
        return add(new Fraction(other));
    }


    private static int gcd(int m, int n) {
        while (n != 0) {
            int r = m % n;
            m = n;
            n = r;
        }
        return m;
    }

    public static void main(String[] args) {
        Fraction f1 = new Fraction(1,2);
        Fraction f2 = new Fraction(2,3);

        System.out.println(f1.add(1));
    }

}

Inheritance

If you ran the program above you probably noticed that the output is not very satisfying. Chances are your output looked something like this:

Fraction@7b11a3ac

The reason is that we have not yet provided a friendly string representation for our Fraction objects. The truth is that, just like in Python, whenever an object is printed by the println or print method it must be converted to string format. In Python you can control how that looks by writing an __str__ method for your class. If you do not then you will get the default, which looked something like the above.

The Object Class

In Java, the equivalent of __str__ is the toString method. Every object in Java already has a toString method defined for it because every class in Java automatically inherits from the Object class. The object class provides default implementations for the following functions.

  • clone
  • equals
  • finalize
  • getClass
  • hashCode
  • notify
  • notifyAll
  • toString
  • wait

We are not interested in most of the functions on that list, and many Java programmers live happy and productive lives without knowing much about most of the functions on that list. However, to make our output nicer we will implement the toString method for the Fraction class. A simple version of the method is provided below.

public String toString() {
    return num + "/" + den;
}

The other important class for us to implement from the list of methods inherited from Object is the equals method. When two objects are compared in Java using the == operator they are tested to see if they are exactly the same object, that is do the two objects occupy the same exact space in the computers memory (Python is operator). This is the default behavior of the equals method provided by Object. The equals method allows us to decide if two objects are equal by looking at their instance variables. However it is important to remember that since Java does not have operator overloading if you want to use your equals method you must call it directly. Therefore once you write your own equals method:

object1 == object2

is NOT the same as

object1.equals(object2)

Here is an equals method for the Fraction class:

public boolean equals(Fraction other) {
    int num1 = this.num * other.getDenominator();
    int num2 = this.den * other.getNumerator();
    return num1 == num2;
}

One important thing to remember about equals is that it only checks to see if two objects are equal it does not have any notion of less than or greater than. We’ll see more about that shortly.

Abstract Classes and Methods

If we want to make our Fraction class behave like Integer, Double, and the other numeric classes in Java We need to make a couple of additional modifications to the class. The first thing we will do is plug Fraction into the Java class hierarchy at the same place as Integer and its siblings. If you look at the documentation for Integer you will see that Integer’s parent class is Number. Number is an abstract class that specifies several methods that all of its children must implement. In Java an abstract class is more than just a placeholder for common functions. In Java an abstract class has the power to specify certain functions that all of its children must implement. You can trace this power back to the strong typing nature of Java.

The that makes the Fraction class a child of Number is as follows:

public class Fraction extends Number {
    ...
}

The keyword extends tells the compiler that the class Fraction extends, or adds new functionality to the Number class. A child class always extends its parent.

The methods we must implement if Fraction is going to be a child of Number are:

  • longValue
  • intValue
  • floatValue
  • doubleValue

This really isn’t much work for us to implement these functions as all we have to do is some conversion of our own and some division. The implementation of these methods is as follows:

public double doubleValue() {
    double numd = num;
    return numd / den;
}

public float floatValue() {
    float numf = num;
    return numf / den;
}

public int intValue() {
    return num / den;
}

public long longValue() {
    long longv = num / den;
    return longv;
}

By having the Fraction class extend the Number class we can now pass a Fraction to any Java function that specifies it can receive a Number as one of its parameters. For example many Java user interface methods accept any object that is a subclass of Number as a parameter. In Java the class hierarchy and the IS-A relationships are very important. Whereas in Python you can pass any kind of object as a parameter to any function the strong typing of Java makes sure that you only pass an object as a parameter that is of the type specified in the function call or one of its children. So, in this case when you see a parameter of type Number its important to remember that an Integer is-a Number and a Double is-a Number and a Fraction is-a Number.

However, and this is a big however, it is also important to remember that if you specify Number as the type on a particular parameter then the Java compiler will only let you use the methods of a Number. In this case longValue, intValue, floatValue, and doubleValue.

Lets suppose you define a method in some class as follows:

public void test(Number a, Number b) {
    a.add(b);
}

The Java compiler would give an error because add is not a defined method of the Number class. Even if you called the add method and passed two Fractions as parameters.

Interfaces

The word interface is used several ways in programming:

  • As a verb or noun about the boundary where things connect
  • the public methods by which a class is connected to the outside
  • The specific Java specification syntax discussed below

Lets turn our attention to making a list of fractions sortable by the standard Java sorting method Collections.sort. In Python 2 all we would need to do is implement the __cmp__ method. But in Java we cannot be that informal. In Java Things that are sortable must be Comparable. Your first thought might be that Comparable is Superclass of Number. That would be a good thought but it would not be correct. Java only supports single inheritance, that is, a class can have only one parent. Although it would be possible to add an additional Layer to the class hierarchy it would also complicate things dramatically. Because Not only are Numbers comparable, but Strings are also Comparable as would many other types. For example we might have a Student class and we want to be able to sort Students by their gpa. But Student already extends the class Person for which we have no natural comparison function.

Java’s answer to this problem is the Interface mechanism. Interfaces are like a combination of Inheritance and contracts all rolled into one. An interface is a specification that says any object that claims it implements this interface must provide the following methods. It sounds a little bit like an abstract class, however it is outside the inheritance mechanism. You can never create an instance of Comparable. Many objects, however, do implement the Comparable interface. What does the Comparable interface specify?

The Comparable interface says that any object that claims to be Comparable must implement the compareTo method. The following is the documentation for the compareTo method as specified by the Comparable interface.

int compareTo(T o)

Compares this object with the specified object for order. Returns a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object. The implementor must ensure sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) for all x and y. (This implies that x.compareTo(y) must throw an exception iff y.compareTo(x) throws an exception.)

The implementor must also ensure that the relation is transitive: (x.compareTo(y)>0 && y.compareTo(z)>0) implies x.compareTo(z)>0.

Finally, the implementor must ensure that x.compareTo(y)==0 implies that sgn(x.compareTo(z)) == sgn(y.compareTo(z)), for all z.

It is strongly recommended, but not strictly required that (x.compareTo(y)==0) == (x.equals(y)). Generally speaking, any class that implements the Comparable interface and violates this condition should clearly indicate this fact. The recommended language is “Note: this class has a natural ordering that is inconsistent with equals.”

In the foregoing description, the notation sgn(expression) designates the mathematical signum function, which is defined to return one of -1, 0, or 1 according to whether the value of expression is negative, zero or positive.

To make our Fraction class Comparable we must modify the class declaration line as follows:

public class Fraction extends Number implements Comparable<Fraction> {
    ...
}

The specification Comparable<Fraction> makes it clear that Fraction is only comparable with another Fraction. The compareTo method could be implemented as follows:

public int compareTo(Fraction other) {
    int num1 = this.num * other.getDenominator();
    int num2 = this.den * other.getNumerator();
    return num1 - num2;
}

With this we can shorten the equals method to just return compareTo(other) == 0.

This math of this implementation is close to the Python 2 __cmp__.

This section described how to use an existing Interface. You can also create them, with what looks like a stripped down class definition: inteface replacing class inthe heading, no instance variables, method headings for public methods followed by a semicolon and no body. Example:

public interface Response
{
    boolean execute(Command cmd); // methods assumed public

    String getCommandName();

    String help();
}

This comes from the large example, http://anh.cs.luc.edu/170/examples/zuul/src/.

Static member variables

Suppose that you wanted to write a Student class so that the class could keep track of the number of students it had created. Although you could do this with a global counter variable that is an ugly solution. The right way to do it is to use a static variable. In Python we could do this as follows:

class Student:
    numStudents = 0

    def __init__(self, id, name):
        self.id = id
        self.name = name

        Student.numStudents = Student.numStudents + 1

def main():
    for i in range(10):
        s = Student(i,"Student-"+str(i))
    print 'The number of students is: ', Student.numStudents

main()

In Java we would write this same example using a static declaration.

public class Student {
        public static int numStudents = 0;

        private int id;
        private String name;

        public Student(int id, String name) {
        this.id = id;
        this.name = name;

        numStudents = numStudents + 1;
        }

        public static void main(String[] args) {
        for(int i = 0; i < 10; i++) {
            Student s = new Student(i,"Student"+i.toString());
        }
        System.out.println(
           "The number of students: "+Student.numStudents.toString());
        }
    }

In this example notice that we create a static member variable by using the static modifier on the variable declaration. Once a variable has been declared static in Java it can be access from inside the class without prefixing the name of the class as we had to do in Python.

Static Methods

We have already discussed the most common static method of all, main. However in our Fraction class we also implemented a method to calculate the greatest common divisor for two fractions (gcd). There is no reason for this method to be a member method since it takes two int values as its parameters. Therefore we declare the method to be a static method of the class. Furthermore since we are only going to use this gcd method for our own purposes we can make it private.

private static int gcd(int m, int n) {
    while (n != 0) {
        int r = m % n;
        m = n;
        n = r;
    }
    return m;
}

Full Implementation of the Fraction Class

A final version of the Fraction class that exercises all of the features we have discussed is as follows.

import java.util.ArrayList;
import java.util.Collections;

public class Fraction extends Number
                      implements Comparable<Fraction> {

    private int num;
    private int den;

    /** Creates a new instance of Fraction */
    public Fraction(int num, int den) {
        this.num = num;
        this.den = den;
    }

    public Fraction(int num) {
        this.num = num;
        this.den = 1;
    }

    public Fraction add(Fraction other) {
        int newNum = other.getDenominator()*this.num +
                     this.den*other.getNumerator();
        int newDen = this.den * other.getDenominator();
        int common = gcd(newNum,newDen);
        return new Fraction(newNum/common, newDen/common );
    }

    public Fraction add(int other) {
        return add(new Fraction(other));
    }

    public int getNumerator() {
        return num;
    }

    public void setNumerator(int num) {
        this.num = num;
    }

    public int getDenominator() {
        return den;
    }

    public void setDenominator(int den) {
        this.den = den;
    }

    public String toString() {
        return num + "/" + den;
    }

    public boolean equals(Fraction other) {
        return compareTo(other) == 0;
    }

    public double doubleValue() {
        double numd = num;
        return numd / den;
    }


    public float floatValue() {
        float numf = num;
        return numf / den;
    }


    public int intValue() {
        return num / den;
    }

    public long longValue() {
        long longv = num / den;
        return longv;
    }
    public int compareTo(Fraction other) {
        int num1 = this.num * other.getDenominator();
        int num2 = this.den * other.getNumerator();
        return num1 - num2;
    }

    private static int gcd(int m, int n) {
        while (n != 0) {
            int r = m % n;
            m = n;
            n = r;
        }
        return m;
    }

    public static void main(String[] args) {
        Fraction f1 = new Fraction(1,2);
        Fraction f2 = new Fraction(2,3);
        Fraction f3 = new Fraction(1,4);

        System.out.println(f1.add(1));
        System.out.println(f1.intValue());
        System.out.println(f1.doubleValue());

        ArrayList<Fraction> myFracs = new ArrayList<Fraction>();
        myFracs.add(f1);
        myFracs.add(f2);
        myFracs.add(f3);
        Collections.sort(myFracs);

        for(Fraction f : myFracs) {
            System.out.println(f);
        }
    }

}

Replacing Python input

The Python input function is so conventient: Supply a prompt, get a value for the line entered.

Consider a class UI that provides that functionality and more.

We will have a number of functions wanting data from the keyboard, new Scanner(System.in). Rather than instantiating a new Scanner in each method, we create it once as a static variable:

import java.util.Scanner;


class UI
{
    private static Scanner in = new Scanner(System.in);

    ...

}

The idea of input is to prompt for data. With the strong typing of Java it makes sense to have versions for different types. The direct analog of input could be

public static String promptLine(String prompt)
{
    System.out.print(prompt);
    return in.nextLine();
}

We can convert the entry on the next line to various types, like int:

public static int promptInt(String prompt)
{
    return Integer.parseInt(promptLine(prompt);
}

Note the mouthful to convert a String to int. This has a major disadvantage: Users can type poorly and enter something that is not an int. Then the program bombs. Here is a way more sophisticated version:

public static int promptInt(String prompt)
{
    System.out.print(prompt);
    while (! in.hasNextInt()) // loop when failure
    {
        in.next();  // dump the bad token
        in.nextLine(); // dump through the newline
        System.out.println("!! Bad int format!!");
        System.out.print(prompt);
    }
    int val = in.nextInt();
    String rest = in.nextLine().trim(); //clear line
    if (rest.length() > 0):
        System.out.println("Skipping rest of input line.");
    return val;
}

Here is a more complete UI class. We introduce the format of documentation that the utility javadoc can turn into separate pages. Javadoc documentation is a multi-line comment starting with /** right before the part being documented. In InteliJ IDEA, you can generate Javadocs: Tools -> Generate Javadocs....

import java.util.Scanner;

/**
 * Aid user keyboard input with prompts and error catching.
 *
 * @version 2017.09.24
 */
public class UI
{
   private static Scanner in = new Scanner(System.in);

   /** Return the Scanner reading the keyboard.
    *  There should be ONLY ONE in a program.
    */
   public static Scanner getKeyboardScanner()
   {
      return in;
   }


   /** Prompt the user for a line and return the line entered.
    *  @param prompt
    *      The prompt for the user to enter the input line.
    *  @return
    *      The line entered by the user.
    */
   public static String promptLine(String prompt) {
      System.out.print(prompt);
      return in.nextLine();
   }


   /** Prompt for a character.
    *  @param prompt
    *      The prompt for the user to enter a character
    *  @return
    *     The first character of the line entered or a blank if the line
    *     is empty.
    */
   public static char promptChar(String prompt)
   {
      String line = promptLine(prompt);
      if (line.length() == 0)
         return ' ';
      return line.charAt(0);
   }

   /** Print a question and return a response.
    *  Repeat until a valid answer is given.
    * @param question
    *    The yes/no question for the user to answer.
    * @return
    *    True if the answer is yes, or False if it is no.
    */
   public static boolean agree(String question)
   {
      String yesStr = "yYtT", noStr = "nNfF",
             legalStr = yesStr + noStr;

      char ans = promptChar(question);
      while (legalStr.indexOf(ans) == -1)
      {
         System.out.println("Respond 'y or 'n':");
         ans = promptChar(question);

      }
      return yesStr.indexOf(ans) >= 0;
   }

   /** Prompt and read an int.
    * Repeat until there is a legal value to return.
    * Read through the end of the line
    *  @param prompt
    *      The prompt for the user to enter a value.
    *  @return
    *      The value entered by the user.
    */
   public static int promptInt(String prompt)
   {
      System.out.print(prompt);
      while (! in.hasNextInt())
      {
         in.next();  // dump the bad token
         in.nextLine(); // dump through the newline
         System.out.println("!! Bad int format!!");
         System.out.print(prompt);
      }
      int val = in.nextInt();
      String rest = in.nextLine().trim(); //clear line
      if (rest.length() > 0)
          System.out.println("Skipping rest of input line.");
      return val;
   }

   /** Prompt and read a double.
    * Repeat until there is a legal value to return.
    *  @param prompt
    *      The prompt for the user to enter a value.
    *  @return
    *      The value entered by the user.
    */
   public static double promptDouble(String prompt)
   {
      System.out.print(prompt);
      while (! in.hasNextDouble())
      {
         in.next();  // dump the bad token
         in.nextLine(); // dump through the newline
         System.out.println("!! Bad double format!!");
         System.out.print(prompt);
      }
      double val = in.nextDouble();
      String rest = in.nextLine().trim(); //clear line
      if (rest.length() > 0)
          System.out.println("Skipping rest of input line.");
      return val;
   }

   /** Prompt and read a line of integers.
    * Repeat until there is a legal line to process.
    *  @param prompt
    *      The prompt for the user to enter data.
    *  @return
    *      The int values entered by the user.
    */
   public static int[] promptIntArray(String prompt)
   {
      while (true) { // exit via return statement
         String line = promptLine(prompt);
         String[] tokens = line.trim().split("\\s+");
         int n = tokens.length;
         int[] nums = new int[n];
         Scanner lineScan = new Scanner(line);
         int i = 0;
         while (i < n && lineScan.hasNextInt() )
         {
            nums[i] = lineScan.nextInt();
            i++;
         }
         if (i == n)
            return nums;
         System.out.format(
           "Bad input %s. Start your line over.\n", tokens[i]);
      }
   }

   /** Prompt and read a line of numbers.
    * Repeat until there is a legal line to process.
    *  @param prompt
    *      The prompt for the user to enter data.
    *  @return
    *      The double values entered by the user.
    */
   public static double[] promptDoubleArray(String prompt)
   {
      while (true) { // exit via return statement
         String line = promptLine(prompt);
         String[] tokens = line.trim().split("\\s+");
         int n = tokens.length;
         double[] nums = new double[n];
         Scanner lineScan = new Scanner(line);
         int i = 0;
         while (i < n && lineScan.hasNextDouble() )
         {
            nums[i] = lineScan.nextDouble();
            i++;
         }
         if (i == n)
            return nums;
         System.out.format(
           "Bad input %s. Start the line over.\n", tokens[i]);
      }
   }
}

Include UI.java in your project that is looking for user input. Here is a simple example, TestUI.java:

class TestUI
{
    public static void main(String[] args])
    {
        String s = UI.promptLine("Enter a line: ");
        int n = UI.promptInt("Repeat how many times? ");
        for (int i = 0; i < n; i++)
            System.out.println(s);
        int[] v = UI.promptIntArray("Nums: ");
        int sum = 0;
        for (int x: v)
            sum += x;
        System.out.println(
           UI.agree("Is the sum " + sum + "? "));
    }
}

Execution might look like:

Enter a line: I like programming.
Repeat how many times? 2
I like programming.
I like programming.
Nums: 3 5 7x 90
Bad input 7x. Start the line over.
Nums: 3 5 7 90
Is the sum 105? zxc
Respond 'y or 'n':
Is the sum 105? Y
true

Cohesion, Coupling, and Separation of Concerns

There are three important ideas in organizing your code into classes and methods:

Cohesion of code is how focused a portion of code is on a unified purpose. This applies to both individual methods and to a class. The higher the cohesion, the easier it is for you to understand a method or a class. Also a cohesive method is easy to reuse in combination with other cohesive methods. A method that does several things is not useful to you if you only want to do one of the things later.

Separation of concerns allows most things related to a class to take place in the class where they are easy to track, separated from other classes. Data most used by one class should probably reside in that class. Cohesion is related to separation of concerns: The data and methods most used by one class should probably reside in that class, so you do not need to go looking elsewhere for important parts. Also, if you need to change something, where concerns are separated into cohesive classes, with the data and logic in one place, it is easier to see what to change, and the changes are likely to be able to be kept internal to the class, affecting only its internal implementation, not its public interface.

Some methods are totally related to the connection between classes, and there may not be a clear candidate for a class to maximize the separation of concerns. One thing to look at is the number of references to different classes. It is likely that the most referred to class is the one where the method should reside.

Coupling is the connections between classes. If there were no connections to a class, no public interface, it would be useless except all by itself. The must be some coupling between classes, where one class uses another, but with increased cohesion and strong separation of concerns you are likely to be able to have looser coupling. Limiting coupling makes it easier to follow your code. There is less jumping around. More important, it is easier to modify the code. There will be less interfacing between classes, so if you need to change the public interface of a class, there are fewer places in other classes that need to be changed to keep in sync.

Aim for strong cohesion, clear separation of concerns, and loose coupling. Together they make your code clearer, easier to modify, and easier to debug.

To see a fairly large, multi-class project, designed to follow the ideas in this section, see the zuul/src subfolder of http://anh.cs.luc.edu/170/examples/. You can put it in a project and it starts frpm class Game. The use of the interface, Response, there has a lot to do with the ideas in this section.

User Defined Object Exercises

  1. The Java Fraction class version is closer in concept to my Python Rational. Help make it closer yet:
    1. Move the simplification of a fraction into the constructor with two parameters, and force the denominator to be positive. Remove the gcd call from add, where it is no longer needed.
    2. Remove the setter methods: henceforth Fractions are immutable.
    3. Create the method mult for multiplication. Create two overloaded methods, one for a Fraction parameter, and one for an int.
    4. Create a neg method that returns the negation of the Rational.
    5. Create another constructor that takes a string parameter, so it can handle "23", "-15/20" and "34.567", like Python Rational.
    6. Elaborate toString so it does not print the /1 for integer values.
    7. With the simplified form of the instance variables, make equals a direct comparison of the numerators and of the denominators.
    8. Implement a pow method for a power, with int parameter. Make sure it works for a negative power.
    9. Little new here, but you could create overloaded sub and div methods for subtraction and division.
    10. Write a separate main class to test all your elaborations.
  2. Convert the Python Animal class and testing code to Java.

Group Project

Objectives

  • Being creative, imagining and describing a program, and working it through to completion
  • Working in a team:
    • Communicating to each other
    • Dividing responsibilities
    • Helping each other
    • Finding consensus
    • Dealing with conflict
    • Providing process feedback
    • Integrating parts created by several people
  • Developing new classes to fit your needs
  • Using the Python docs or Java API docs
  • Designing a program for refinement
  • Testing
  • Evolving a program
  • Creating documentation for user and implementers
  • Programming in the large – not a small predefined problem
  • Make something that is fun to write

If you want a text adventure game, you might start from the codebase in http://anh.cs.luc.edu/170/examples/zuul/. There are advantages to planning something from scratch, or alternately modifying a give codebase that is mostly a core of what you want. Reading a major project is also a way to gain familiarity with a language.

What to turn in:

  • Periodic reports
  • intermediate implementation
  • a final presentation
  • a final implementation including user and programmer documentation and process documentation
  • individual evaluations

Overview

You will be assigned to groups of 3-4 people to work on a project that will extend until the end of the semester, with little other work introduced in class, and of course your last exam. This leaves a junk of course time for concentrating on the project. This ends with presentations in final exam period. Your group will be designing and implementing a project of your choosing. You could choose to make a text based game, starting from our game skeleton or not, or something completely different, started from scratch. The project should have some clear focus or aim, For instance a game should be able to be won.

Start by brainstorming and listing ideas – do not criticize ideas at this point. That is what is meant by brainstorming - having your internal critic going inhibits creativity. After you have a large list from brainstorming, it is time to think more practically and settle on one basic situation, and think of a considerable list of features you would like it to have. Order the features, considering importance, apparent ease of development, and what depends on what else. Remember iterative development! Get something simple working, and then add a few features at a time, testing the pieces added and the whole project so far. Test, debug, and make sure the program works completely before using your past experience to decide what to add next. This may different than what you imagined before the work on your first stage! Like the provided base project, early stages do not need to be full featured, but make sure that you are building up to a version with an aim, and which includes interesting features. You should end with a program that has enough instructions provided for the user, so someone who knows nothing of your implementation process or intentions can use the program successfully. Your implementation should also be documented, imagining that a new team of programmers is about to take over after your departure, looking to create yet another version.

Your Team

Your instructor will tell you about team makeup.

There are a number of roles that must be filled by team members. Some will be shared between all members, like coder, but for each role there should be a lead person who makes sure all the contributions come together. Each person will have more than one role. All members are expected to pull their own weight, though not all in exactly the same roles. Everyone should make sustained contributions, documented in the periodic reports. Understand that this project will be the major course commitment for the rest of the semester. These roles vary from rather small to central. Not all are important immediately.

Roles

  1. Leader: Makes sure the team is coordinated, encourage consensus on the overall plan, oversee that the agreements are carried through, be available as contact person for the team and the TA and your instructor.
  2. Lead programmer: Keep track of different people’s parts to make sure they fit together.
  3. Coder/unit tester: Everyone must have a significant but not necessarily equal part in this job. Each person should have primary responsibility for one or more cohesive substantive units, and code and test and be particularly familiar with those parts. Do your best to make individual parts be cohesive and loosely coupled with other people’s work, to save a lot of pain in the testing and debugging phase. When coding, you are still encouraged to do pair programming, though what pair from the team is working together at different times may be fluid. The lead programmer might be involved in pairs more often than others, but be sure the other coders get to “drive” often.
  4. Librarian/version coordinator: The default should be for you to have a box.com folder shared with your whole team and me and your TA, with all as editors. Your folder should have the name of your team. You should always have a folder that contains the latest working version of the project. You should also keep old versions, for instance copied into numbered version folders. Box does not handle successive versions automatically. You can choose to use the more capable professional version control combination of git and github (built into IntelliJ and PyCharm). Git will have a major learning curve, and in that case this person should be the best informed on git, and help the other team members.
  5. Report coordinator: Gather the contributions for reports from team members and make sure the whole reports get to posted on schedule. Your instructor needs a clear idea of the contributions of each member in each report interval. If a team member is not clear on this to the report writer, the report writer needs to be insistent.
  6. Instruction coordinator: Make sure there are clear written documents and help within the program for the user, who you assume is not a programmer and knows nothing about your program at the start.
  7. Documentation coordinator: Make sure the documentation is clear for method users/consumers. This includes the documentation for programmers before the headings of methods and classes. This is for any time someone wants to use (not rewrite) a class or method you wrote. Also have implementer documentation, for someone who will want to modify or debug your code and needs to understand the details of your internal implementations. The extent of this can be greatly minimized by good naming.
  8. Quality manager: Take charge of integrated tests of the whole program (following coder’s unit tests). Make sure deficiencies are addressed.

Conflict resolution: You will certainly have disagreements and possibly conflicts. Try to resolve them within the team. When that is not working, anyone can go to the instructor with a problem. Do not delay coming to me if things are clearly not working with the team.

The process

Initial:

  1. Agree on roles. These roles can change if necessary, but you are encouraged to stick with them for simplicity and consistency.
  2. Agree on a team name and a short no-space abbreviation if necessary, and let me know it.
  3. Brainstorm about the project. Distill the ideas into a direction and overall goals.

On individual versions (Two formal versions will be required):

  1. Break out specific goals for the version. How are you heading for your overall goals? Are you biting off a significant and manageable amount? You are expected to check in with me on this part and the initial plans before moving very far. This will be new for most of you.
  2. Plan and organize the necessary parts and the interfaces between the parts.
  3. Write the interface documentation for consumers of the code for the parts you plan to write. Agree on them. You need to do this eventually anyway. Agreement up front can save you an enormous amount of time! Do not let the gung-ho hackers take off before you agree on documented interfaces. We have seen it happen: If you do not put your foot down, you are stuck with a bad plan that will complicate things. Otherwise lots of code needs to be rethought and rewritten.
  4. If more than one person is working on the same class, plan the names, meanings, and restrictions on the private instance variables – all coders should be assuming the same things about the instance variables! Also agree on documentation for any private helping methods you share.
  5. Code to match the agreed consumer interface and class implementation designs.
  6. Check each other’s code.
  7. Do unit tests on your own work, and fix them and test again...
  8. Do overall tests of everything together, and fix and test again...
  9. Look back at what you did, how it went, what you could do better, and what to change in your process for the next version.

You are strongly encouraged to follow modern programming practice which involves splitting each of these formal versions into much smaller steps that can be completed and tested following a similar process. Order pieces so you only need to code a little bit more before testing and combining with pieces that already work. This is enormously helpful in isolating bugs! This is really important. If you thought you spent a long while fighting bugs in your small homework assignment, that is nothing compared to the time you can spend with a large project, particularly if you make a lot of haphazard changes all at once.

Splitting Up The Coding

Make good use of the separation of public interface and detailed implementation. If your project has loosely coupled class, the main part of the public interface should be limited and easy to comprehend.

Ideally have one individual (or pair) assigned a whole class. One useful feature for allowing compiling is to first generate a stub file that includes the public interface documentation, headings, and dummy return values and compiles but does nothing. Post this under a box folder for the current version number. You will then provide your team members with something that tells them what they can use and allows them to compile their own part. Then later substitute more functional classes.

Your instructor and you will want to review your code. We do not want to have to reread almost the same thing over and over: Use the editor copy command with extreme caution. If you are considering making an exact copy, clearly use a common method instead. If you copy and then make substitutions in the same places, you are likely better off with a method with the common parts and with parameters inserted where there are differences. You can make a quick test with a couple of copied portions, but then convert to using a method with parameters for the substitutions. Besides being a waste of effort to define seven methods each defining a tool, with just a few strings differing from one method to the next, we will require you to rewrite it, with one method with parameters, and just seven different calls to the method with different parameters. Save yourself trouble and do it that way the first time, or at least after you code a second method and see how much it is like the first one you coded....

If you are making many substitutions of static textual data, put the data into a resource file like it hte sample Zuul project.

You only want to commit working code into the shared current version folder. Comment out incomplete additions that you want to show to everyone, or comment out the call to a method that compiles but does not yet function logically. An alternative is to have a separate folder for in-process code to share for comment, so you will not try to compile it with the current working version.

Incremental reports

Reports are due from the report writer on a schedule specified by the instructor, likely weekly for a full semester course, twice weekly for a half semester course.

  1. Inside your team’s box folder have a subfolder called Reports. A sample stub form to fill out on the computer is in incremental-report.rtf. Make the name of each incremental report document be the date it was due, like Mar26.rtf. It is easy to copy the table from this week to last week and edit it to show how much your plans matched reality. You should post a version for your team to look at first. Please distinguish drafts from the final version for me to look at. You might have a separate folder Drafts, and move the report into the Reports folder when it is final. Box easily allows moving files, but not renaming them.
  2. One report should by each time one is due, with the person in the role of report writer making sure a complete version is produced and placed in the Reports folder.
  3. Under plans for the next reporting period, include concrete tasks planned to be completed, and who will do them, with an informative explanation. The content and depth of the person’s work should be clear. If you can state that clearly and be brief, great. The tasks do not only include coding: they can be any of the parts listed above, and for any particular part of the project, where that makes sense. If individuals cannot state clearly what they are working on, then the team leader and lead coder have a significant issue in their leadership that needs to be addressed.
  4. In the review of the plans from the previous report (2nd report onward) include the previous plans and what actually happened, task by task, concretely, with enough detail to give an idea on the magnitude of the work. This can include the portion completed and/or changes in the plans and their reasons. “Still working on X” is not useful: Who was doing what? What methods, doing what, were completed? Which are in process? Which are being debugged? What part remains to be done, and who is it assigned to? The report writer is responsible to get a clear statement from each team member.

Intermediate deliverables

These materials should be placed in a subfolder of Reports call Intermediate of your team project folder. See the due date in the schedule.

  • Copy the linked stub of intermediate-report.rtf document. Then complete it:
    • List the project roles again, and who ended up filling them. For coding, say who was the person primarily responsible for each part.
    • If you used old classes, like those from the skeleton project or a lab or somewhere else, say which ones are included unchanged or give a summary of changes.
    • If your documentation of methods is not generally done, say what classes got clear documentation (or individual methods if only some were done).
    • Where are you planning to go from here, and who you envision being primarily responsible for different parts?
  • Include parts 2-4 listed below under Final Deliverables, but for an intermediate version that runs, and does not need to have the goal working yet. Have documentation of your methods, including summary description and description of parameters and return values. If for some reason you do not have all the documentation that you were encouraged to write first, at least be sure to have and point out significant examples of your clear documentation of purpose, parameters, and return values. This allows instructor feedback for completing the rest.
  • The idea is to have everyone get an idea of what is expected, so we have no misunderstandings about the final version. We will give you feedback from this version to incorporate in the final version. We do not want to have to say anyone did anything “wrong” on the final version. We want to be able to concentrate on your creative accomplishments.
  • Look through the list of deliverables again and make sure your collection is complete.

Final Deliverables

Group Submission:

One submission of the group work is due one hour before the final presentations.

  1. All files listed in parts 2-5. Also include a zip file, named with your team abbreviation, containing a Windows executable with (a separate copy of) any other image and data files needed. Test to make sure you can unzip and run the executable. The final submissions will be accessible to the whole class – so we can all play them!
  2. Source code. You can name the classes appropriately for the content of your game.
  3. User instructions. These should be partly built into the program. The most extensive documentation may be in a document file separate from the program, if you like. (Plain text, MS Word, Rich text (rtf), or PDF, please.) The starting message built into the beginning of the game should mention the file name of such external documentation, if you have it.
  4. Programmer documentation. Document the public interface for all methods in comments directly before the method heading. Add implementation comments embedded in the code where they add clarity (not just verbosity). You may have a separate overview document. Include “Overview” in the file name
  5. Overall project and process review in a document named like the linked stub, final-review.rtf
    • The first section should be Changes. So the instructor does not duplicate effort, please give an overview of the changes from the intermediate version. What classes are the same? What features were added? What classes are new? Which classes or methods were given major rewrites? What classes had only a few changes? (In this case try to list what to look for.)
    • List again the roles, and who filled them. For coding, say who was the person primarily responsible for each part.
    • What did you learn? What were the biggest challenges? What would you do differently the next time? What are you most proud of?
    • How could we administer this project better? What particularly worked about the structure we set up?
  6. A 10-15 minute presentation of your work to the class in final exam period. What would you want to hear about other projects? (Say it about yours.) What was the overall idea? What was the overall organization? What did you learn that was beyond the regular class topics that others might find useful to know? What were your biggest challenges? Do not show off all your code just because it is there. Show specific bits that gave you trouble or otherwise are instructive, if you like.

Look through the list of deliverables again, before sending files, and check with the whole team to make sure your collection is complete.

Your Assessment of Individuals in the Group:

This is due electronically 10 minutes after the final class presentation period, from each team member, independently, turned in a manner specified by your instructor, like other homework assignments.

Change the name of the linked stub file Indiv-Mem-Assessment.rtf to your teamAbbreviation-yourName.rtf. You may want to tweak it after the group presentation, but have it essentially done beforehand.

Writing this is NOT a part of your collective group deliberations. It is individual in two senses: both in being about individual team members and in being the view of one individual, you. For this document only, everyone should be writing separately, privately, and independently from individual experience. If you lack data on some point, say so, rather than using what others are saying.

Common Mistakes

  • Forgetting to declare your variables.

    Histo.java:21: cannot find symbol
    symbol  : variable count
    location: class Histo
            count = new ArrayList<Integer>(10);
            ^
    
  • Not importing a class:

    Histo.java:9: cannot find symbol
    symbol  : class Scanner
    location: class Histo
            Scanner data = null;
            ^
    
  • Forgetting to use the new keyword to create an object. Here’s an example of the error message that occurs when you forget to use the new keyword. Notice that the message is pretty unhelpful. Java thinks you are trying to call the Method Scanner, but there are two problems. First Scanner is not really a method it is a constructor.:

    Histo.java:14: cannot find symbol
    symbol  : method Scanner(java.io.File)
    location: class Histo
                    data = Scanner(new File("test.dat"));
                           ^
    
  • Forgetting a Semicolon:

    Histo.java:19:
    ';' expected
                System.exit(0);
                ^
    
  • Forgetting to declare the kind of object in a container:

    Note: Histo.java uses unchecked or unsafe operations. Note:
    Recompile with -Xlint:unchecked for details.
    
  • Too many semicolons: Although you need to add a lot of semicolons if you are used to Python, there are places where adding one causes a logical error: A semicolon by itself is a legal (do-nothing) statement, like pass in Python.

    if ( x < 0); // WRONG PROBABLY!
        System.out.println("negative"));
    

    A basic if statement has a single following statement bound to it. Above, the semicolon is the statement – The printing statement is not. The printing is always executed after this if statement.

    There is the same issue with a semicolon after a while statement condition.

  • Match Wrong if With else

    In Python it is easy to find the if that goes with an else: look upward to find an if with the same indentation. In Java you are encouraged to indent much like Python, but it has no intrinsic meaning.

    If you do not consistently put the sub-statements for the true and false choices inside braces, you can run into problems from the fact that the else part of an if statement is optional. Even if you use braces consistently, you may well need to read code that does not place braces around single statements. If Java understood indentation as in the recommended formatting style (or as required in Python), the following would be OK:

    if (x > 0)
       if (y > 0)
          System.out.println("positive x and y");
    else
       System.out.println("x not positive, untested y");
    

    Unfortunately placing the else under the first if is not enough to make them go together. (Remember the Java compiler ignores extra whitespace). The following is equivalent to the compiler, with the else apparently going with the second if:

    if (x > 0)
       if (y > 0)
          System.out.println("positive x and y");
       else
          System.out.println("x not positive, untested y");
    

    The compiler is consistent with the latter visual pattern: an else goes with the most recent if that could still take an else. Hence if x is 3 and y is -2, the else part is executed and the statement printed is incorrect: in this code the else clause is only executed when x is positive and y (is tested and) is not positive.

    If you put braces everywhere to reinforce your indentation, as we suggest, or if you only add the following one set of braces around the inner if statement:

    if (x > 0) {
       if (y > 0)
          System.out.println("positive x and y");
    }
    else
       System.out.println("x not positive, untested y");
    

    then the braces enclosing the inner if statement make it impossible for the inner if to continue on to an optional else part. The else must go with the first if. Now when the else part is reached, the statement printed will be true: x is not positive, and the test of y was skipped.

Naming Conventions

Java has some very handy naming conventions.

.._java-documentation-online:

Java Documentation Online

All Java class libraries are documented and available online. Here is the official resource: https://docs.oracle.com/javase/8/docs/api/index.html?overview-summary.html

If you know the package that a class is in, you can reach it through the package. If you do not know the package, you can go to the Index, click the link for the starting letter. Then you can do a browser search in the (huge) page. Class entries have the class name, a space and a dash. Searching for that combination may help.

In general the Javadoc page for any class contains information about:

Javadoc pages are constructed from the source code where the class is implemented, using the javadoc utility. This encourages Java programmers to do a good job of documenting their code, while providing a user friendly way to read the documentation without looking at the code directly.

Shameless Plug

If you got this far, I would also like to use this space as a shameless plug for two books. At Luther college we use Python for CS1 and CS2. When we decided to make the switch to introducing CS with Python we wanted to provide our students with two semesters of Python. The reason is that after one semester students are just getting comfortable with the language and it does not make sense to push them into a brand new language just as they are getting some comfort. The second reason is that Python really is a great language for teaching data structures. The language makes the algorithms evident by clearing away all of the syntactic clutter. So we (David Ranum and I) wrote a CS2 book called Problem Solving with Algorithms and Data Structures using Python. After we finished the CS2 book we decided to write our own CS1 book as well. This book will be available for Fall 2008 classes from Jones and Bartlett publishers. The tentative title is Python in Context. Please feel free to contact me for more information.