Java for C++ Programmers

(prepared by Prof.  Marvin Solomon (U. Wisconsin), modified by Marius Zimand (Towson University), further modified for this course)

Contents


Introduction

The Java API

The Java langauge is actually rather small and simple – an order of magnitude smaller and simpler than C++, and in some ways, even smaller and simpler than C. However, it comes with a very large and constantly growing library of utility classes. Fortunately, you only need to know about the parts of this library that you really need, you can learn about it a little at a time, and there is excellent, browsable, on-line documentation. These libraries are grouped into packages. One set of packages, called the Java 2 Platform API, comes bundled with the language.

A First Example

Large parts of Java are identical to C++. For example, the following procedure, which sorts an array of integers using insertion sort, can be used in C++ or Java.

/** Sort the array a[] in ascending order
 ** using an insertion sort.
 */
void sort(int a[], int size) {
    for (int i = 1; i < size; i++) {
        // a[0..i-1] is sorted
        // insert a[i] in the proper place
        int x = a[i];
        for (int j = i-1; j >=0; --j) {
            if (a[j] <= x)
                break;
            a[j+1] = a[j];
        }
        // now a[0..j] are all <= x
        // and a[j+2..i] are > x
        a[j+1] = x;
    }
}

Note that the syntax of control structures (such as for and if), assignment statements, variable declarations, and comments are all mostly the same in Java as in C++. One exception is the addition of a Javadoc comment, a form of structured comment which is enclosed in /** ... */ (note the double asterisks) and can contain documentation tags such as @param and @return. You can read more about this code comment format by searching for "javadoc" on the WWW.

Although the code can be used unmodified in Java and C++, one difference regarding arrays should be mentioned: Java generally uses the syntax int[] a rather than int a[] to declare arrays. This means that there is no need to put the name of the array reference (a) "inside" the type name (int[]).

To test this procedure in a C++ program, we might use a ``main program'' like this:

#include <iostream.h>
#include <stdlib.h>
extern "C" long random();
/** Test program to test sort */
int main(int argc, char *argv[]) {
    if (argc != 2) {
        cerr << "usage: sort array-size" << endl;
        exit(1);
    }
    int size = atoi(argv[1]);
    int *test = new int[size];
    for (int i = 0; i < size; i++)
        test[i] = random() % 100;
    cout << "before" << endl;
    for (int i = 0; i < size; i++)
        cout << " " << test[i];
    cout << endl;
    sort(test, size);
    cout << "after" << endl;
    for (int i = 0; i < size; i++)
        cout << " " << test[i];
    cout << endl;
    return 0;
}

A Java program to test the sort procedure is different in a few ways. Here is a complete Java program using the sort procedure.

import java.io.*;
import java.util.Random;
 
class SortTest {
    /** Sort the array a[] in ascending order
     ** using an insertion sort.
     */
    static void sort(int[] a, int size) {
        for (int i = 1; i < size; i++) {
            // a[0..i-1] is sorted
            // insert a[i] in the proper place
            int x = a[i];
            for (int j = i-1; j >=0; --j) {
                if (a[j] <= x)
                    break;
                a[j+1] = a[j];
            }
            // now a[0..j] are all <= x
            // and a[j+2..i] are > x
            a[j+1] = x;
        }
    }
 
    /** Test program to test sort */
    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("usage: sort array-size");
            System.exit(1);
        }
        int size = Integer.parseInt(args[0]);
        int[] test = new int[size];
        Random r = new Random();
 
        for (int i = 0; i < size; i++)
            test[i] = (int)(r.nextFloat() * 100);
        System.out.println("before");
        for (int i = 0; i < size; i++)
            System.out.print(" " + test[i]);
        System.out.println();
 
        sort(test, size);
 
        System.out.println("after");
        for (int i = 0; i < size; i++)
            System.out.print(" " + test[i]);
        System.out.println();
 
        System.exit(0);
    }
}

To try it out, create a new directory and copy the example to a file named SortTest.java in that directory.  The file must be called SortTest.java!

    javac SortTest.java
    java SortTest 10

The javac command invokes the Java compiler on the source file SortTest.java. If all goes well, it will create a file named SortTest.class, which contains code for the Java virtual machine. The java command invokes the Java interpreter to run the code for class SortTest. Note that the first parameter is SortTest, not SortTest.class or SortTest.java because it is the name of a class, not a file.

There are several things to note about this program. First, Java has no ``top-level'' or ``global'' variables or functions. A Java program is always a set of class definitions. Thus, we had to make sort and main member functions (called ``methods'' in Java) of a class, which we called SortTest.

Second, the main function is handled somewhat differently in Java from C++. In C++, the first function to be executed is always a function called main, which has two arguments and returns an integer value. The return value is the ``exit status'' of the program; by convention, a status of zero means ``normal termination'' and anything else means something went wrong. The first argument is the number of words on the command-line that invoked the program, and the second argument is a an array of character strings (denoted char *argv[] in C++) containing those words. If we invoke the program by typing

    sort 10

we will find that argc==2, argv[0]=="sort", and argv[1]=="10".

In Java, the first thing executed is the method called main of the indicated class (in this case SortTest). The main method does not return any value (it is of type void). For now, ignore the words ``public static'' preceding void. We will return to these later. The main method takes only one parameter, an array of strings (denoted String argv[] in Java). This array will have one element for each word on the command line following the name of the class being executed. Thus in our example call,

    java SortTest 10

argv[0] == "10". There is no separate argument to tell you how many words there are, but in Java, you can tell how big any array is by using length. In this case argv.length == 1, meaning argv contains only one word.

The third difference to note is the way I/O is done in Java. System.out in Java is roughly equivalent to cout in C++ (or stdout in C), and

    System.out.println(whatever);

is (even more) roughly equivalent to

    cout << whatever << endl;

Our C++ program used three functions from the standard library, atoi, random, and exit. Integer.parseInt does the same thing as atoi: It converts the character-string "10" to the integer value ten, and System.exit(1) does the same thing as exit(1): It immediately terminates the program, returning an exit status of 1 (meaning something's wrong). The library class Random defines random-number generators. The statement Random r = new Random() create an instance of this class, and r.nextFloat() uses it to generate a floating point number between 0 and 1. The cast (int) means the same thing in Java as in C++. It converts its floating-point argument to an integer, throwing away the fraction.

Finally, note that the #include directives from C++ have been replaced by import declarations. Although they have roughly the same effect, the mechanisms are different. In C++, #include <iostream.h> pulls in a source file called iostream.h from a source library and compiles it along with the rest of the program. #include is usually used to include files containing declarations of library functions and classes, but the file could contain any C++ source code whatever. The Java declaration import java.util.Random merely tells the compiler that whenever you write Random in this file, what you really mean is a class in the java.util package, whose full name is java.util.Random. By itself, the import statement does not cause additional code to be included in an application. The next section explains more about packages.

Names, Packages, and Separate Compilation

As in C or C++, case is significant in identifiers in Java. Aside from a few reserved words, like if, while, etc., the Java langauge places no restrictions on what names you use for functions, variables, classes, etc. However, there is a standard naming convention, which all the standard Java libraries follow, and which you must follow in this class.

A more extensive set of guidelines is included in the Java Language Specification.

Simple class definitions in Java look rather like class definitions in C++ (although, as we shall see later, there are important differences).

    class Pair { int x, y; }

Each class definition should go in a separate file, and the name of the source file must be exactly the same (including case) as the name of the class, with ".java" appended. For example, the definition of Pair must go in file Pair.java. The file is compiled as shown above and produces a .class file. There are exceptions to the rule that requires a separate source file for each class. In particular, class definitions may be nested. However, this is an advanced feature of Java which will not be discussed here.

There is a large set of predefined classes, grouped into packages. The full name of one of these predefined classes includes the name of the package as prefix. We already saw the class java.util.Random. The import statement allows you to omit the package name from one of these classes. Because the SortTest program starts with

    import java.util.Random;

we can write

    Random r = new Random();

rather than

    java.util.Random r = new java.util.Random();

You can import all the classes in a package at once with a notation like

    import java.util.*;
    import java.io.*;

This is not necessarily recommended, though. Suppose that you import all classes in java.util, when all you really want is java.util.Random. Suppose that in the next version of Java, there also happens to be a class named java.io.Random. Suddely you have imported two classes named Random, and the compiler can no longer determine which class you mean, which results in a compilation error. (If this example seems contrived, consider the Java 1.0 class java.awt.List, which represents a GUI list, and the Java 1.2 addition java.util.List, which represents a standard list datatype.)

The package java.lang is special; every program behaves as if it started with

    import java.lang.*;

whether it does or not. You can, and should, define your own packages for the code you write; this is as simple as writing the statement package some.package.name at the top of a source file (or using the support provided from an integrated development environment). Package naming should be unique; by convention, package naming begins with your domain name in reverse, as in se.liu.ida. This means that you never have to worry about name clashes with packages created outside IDA, which only leaves the task of finding a name which is unique within this domain. Such a name could be created by using your email address, as in se.liu.ida.noone123.tddi48.project.

Values, Objects, and Pointers

It is sometimes said that Java doesn't have pointers. That is not true. In fact, objects can only be referenced with pointers. More precisely, variables can hold primitive values (such as integers or floating-point numbers) or references (pointers) to objects. A variable cannot hold an object, and you cannot make a pointer to a primitive value. Since you don't have a choice, Java doesn't have a special notation like C++ does to indicate when you want to use a pointer.

There are exactly eight primitive types in Java, boolean, char, byte, short, int, long, float, and double. Most of these are similar to types with the same name in C++. We mention only the differences.

A boolean value is either true or false. You cannot use an integer where a boolean is required (e.g. in an if or while statement) nor is there any automatic conversion between boolean and integer.

A char value is 16 bits rather than 8 bits, as it is in C or C++, to allow for all sorts of international alphabets. As a practical matter, however, you are unlikely to notice the difference. The byte type is an 8-bit signed integer (like signed char in C or C++).

A short is 16 bits and an int is 32 bits, just as in C or C++ on most modern machines (in C++ the size is machine-dependent, but in Java it is guaranteed to be 32 bits). A Java long is not the same as in C++; it is 64 bits long--twice as big as a normal int--so it can hold any value from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807. The types float and double are just like in C++: 32-bit and 64-bit floating point.

A difference worth pointing out is that all Java primitive types are signed. This means that C++ declarations like the following are not allowed in Java:

    unsigned long bigLong;     // not legal in Java
    unsigned double salary;    // not legal in Java 

As in C++, objects are instances of classes. There is no prefix * or & operator or infix -> operator.

As an example, consider the class declaration (which is the same in C++ and in Java)

 
class Pair { int x, y; }

C++

Java

Pair mypair;

Cannot declare a variable which "contains" a Pair; can only declare a variable that "contains" a pointer to a pair as shown in the next line

Pair *origin = new Pair();

Pair origin = new Pair();

Pair *p, *q, *r;

Pair p, q, r;

Pair **s;

Cannot declare a pointer to a pointer.

origin->x = 0;

origin.x = 0;

p = new Pair;

p = new Pair();

p -> y = 5;

p.y = 5;

r = &mypair;

Could not declare mypair as a variable "containing" an object, which means there is no need for an "&" operator to find the address of the object

r = origin;

r = origin;
This is pointer assignment

As in C or C++, arguments to a Java procedure are passed ``by value'':

    void f() {
        int n = 1;
        Pair p = new Pair();
        p.x = 2; p.y = 3;
        System.out.println(n);    // prints 1
        System.out.println(p.x);  // prints 2
        g(n,p);
        System.out.println(n);    // still prints 1
        System.out.println(p.x);  // prints 100
    }
    void g(int num, Pair ptr) {
        System.out.println(num);  // prints 1
        num = 17;                 // changes only the local copy
        System.out.println(num);  // prints 17
        System.out.println(ptr.x);// prints 2
        ptr.x = 100;              // changes the x field of caller's Pair
        ptr = null;               // changes only the local ptr
    }

The formal parameters num and ptr are local variables in the procedure g initialized with copies of the values of n and p. Any changes to num and ptr affect only the copies. However, since ptr and p point to the same object, the assignment to ptr.x in g changes the value of p.x.

Unlike C++, Java has no way of declaring reference parameters, and unlike C++ or C, Java has no way of creating a pointer to a (non-object) value, so you can't do something like this:

    /* C or C++ */
    void swap1(int *xp, int *yp) {
        int tmp;
        tmp = *xp;
        *xp = *yp;
        *yp = tmp;
    }
    int foo = 10, bar = 20;
    swap1(&foo, &bar);  /* now foo==20 and bar==10 */
 
    // C++ only
    void swap2(int &xp, int &yp) {
        int tmp;
        tmp = xp;
        xp = yp;
        yp = tmp;
    }
    int this_one = 88, that_one = 99;
    swap2(this_one, that_one);  // now this_one==99 and that_one==88

You'll probably miss reference parameters most in situations where you want a procedure to return more than one value. As a work-around you can return an object or array or pass in a pointer to an object.

Structures and Unions

One of the unfortunate problems with C++ has been its support for compiling legacy, or pre-existing, C code. Of course, this probably also has been the key reason that C++ has gained the widespread acceptance that it has. The drawback to continuing to provide support for pre-existing code is that it muddies the language, allowing and sometimes forcing programmers to create new code that is more difficult to understand than would otherwise be necessary.

Because C++ added to C the ability to define classes, it made superfluous the need to define structures and unions. Because Java is a new language with no requirement to support an existing base of code, its object model is much cleaner. In Java, you define classes. The C concepts of struct and union have been removed.

Garbage Collection

New objects are created by the new operator in Java just like C++ (except that an argument list is required after the class name, even if the constructor for the class doesn't take any arguments so the list is empty). However, there is no delete operator. The Java system automatically deletes objects when no references to them remain. This is a much more important convenience than it may at first seem. delete operator is extremely error-prone. Deleting objects too early can lead to dangling reference, as in

    p = new Pair();
    // ...
    q = p;
    // ... much later
    delete p;
    q -> x  = 5; // oops!

while deleting them too late (or not at all) can lead to garbage, also known as a storage leak.

Static, Final, Public, and Private

Just as in C++, it is possible to restrict access to members of a class by declaring them private, but the syntax is different.
In C++:

    class C {
        private:
            int i;
            double d;
        public:
            int j;
            void f() { /*...*/ }
    }

In Java:

    class C {
        private    int i;
        public     int j;
        private    double d;
        public     void f() { /* ... */ }
    }

As in C++, private members can only be accessed from inside the bodies of methods (function members) of the class, not ``from the outside.'' Thus if c is an instance of Circle, c.i is not legal if written within class Rectangle, but i can be accessed from the body of any method within class Circle. (protected is also supported; it means the same thing as it does in C++). The default (if neither public nor private nor protected is specified) is that a member can be accessed from anywhere in the same package, giving a facility rather like ``friends'' in C++.

The keyword static also means the same thing in Java as C++, which is not what the word implies: Ordinary members have one copy per instance, whereas a static member has only one copy, which is shared by all instances of the class. In effect, a static member lives in the class itself, rather than in the instances.

    class C {
        int x = 1;  // by the way, this is ok in Java but not C++
        static int y = 1;
        void f(int n) { x += n; }
        static int g() { return ++y; }
    }
    C p = new C();
    C q = new C();
    p.f(3);
    q.f(5);
    System.out.println(p.x);  // prints 4
    System.out.println(q.x);  // prints 6
    System.out.println(C.y);  // prints 1
    System.out.println(p.y);  // means the same thing
    System.out.println(C.g());// prints 2
    System.out.println(q.g());// prints 3

Static members are often used instead of global variables and functions, which do not exist in Java. For example,

    Math.tan(x);            // tan is a static method of class Math
    Math.PI;                // a static "field" of class Math with value 3.14159...
    Integer.parseInt("10"); // used in the sorting example

The keyword final is roughly equivalent to const in C++: final fields cannot be changed. It is often used in conjunction with static to defined named constants.

    class Card {
        public int suit = CLUBS;     // default
        public final static int CLUBS = 1;
        public final static int DIAMONDS = 2;
        public final static int HEARTS = 3;
        public final static int SPADES = 4;
    }
    Card c = new Card();
    c.suit = Card.SPADES;

Beware, however, that if you declare an object variable as final, only the pointer is final. The object that the pointer points to can still be modified! This means that "final" is no true replacement for "const".

Arrays

In Java, arrays are objects. Like all objects in Java, you can only point to them, but unlike a C++ variable, which is treated like a pointer to the first element of the array, a Java array variable points to the whole object. There is no way to point to a particular slot in an array.

Each array has a read-only (final) field length that tells you how many elements it has. The elements are numbered starting at zero as in C++: a[0] ... a[a.length-1]. Once you create an array (using new), you can't change its size. If you need more space, you have to create a new (larger) array and copy over the elements (but see the library class ArrayList below).

    int x = 3;   // a value 
    int[] a;     // a pointer to an array object; initially null
    int a[];     // means exactly the same thing (for compatibility with C)
    a = new int[10]; // now a points to an array object
    a[3] = 17;   // accesses one of the slots in the array
    a = new int[5]; // assigns a different array to a
                 // the old array is inaccessible (and so
                 // is garbage-collected)
    int[] b = a; // a and b share the same array object
    System.out.println(a.length); // prints 5

In C++ you must specify the size or dimension of the array in the declaration. In Java, this is not necessary (or even allowed) because Java requires that all arrays be allocated with new. It is not possible in Java to allocate the equivalent of a C automatic array. To allocate an array using new, you would use code similar to that shown in the following examples:

int intArray[] = new int[100];
float floatArray[];
floatArray = new float[100];
long [] longArray = new long[100];
double [][] doubleArray = new double[10][10];

From these examples, you can see that memory can be allocated on the same line on which the array is declared, as was done with intArray. Or, the array can be declared and allocated on two separate lines, as with floatArray. The variable doubleArray shows how to declare and allocate a multidimensional array in Java. In this case, a two-dimensional array is allocated. This is really an array of arrays in which each of 10 first dimension arrays contains its own array of 10 items.

An alternative way of allocating a Java array is to specify a list of element initializers when the array is declared. This is done as follows:

int intArray[] = {1,2,3,4,5};
char [] charArray = {'a', 'b', 'c'};
String [] stringArray = {"A", "Four", "Element", "Array"};

In this case, intArray will be a five-element array holding the values 1 through 5. The three-element array charArray will hold the characters 'a', 'b', and 'c'. Finally, stringArray will hold the four strings shown.

Strings

Since you can make an array of anything, you can make an an array of char or an an array of byte, but Java has something much better: the type String. The + operator is overloaded on Strings to mean concatenation. What's more, you can concatenate anything with a string; Java automatically converts it to a string. Built-in types such as numbers are converted in the obvious way. Objects are converted by calling their toString() methods. Library classes all have toString methods that do something reasonable. You should do likewise for most classes you define. This is great for debugging.

    String s = "hello";
    String t = "world";
    System.out.println(s + ", " + t);       // prints "hello, world"
    System.out.println(s + "1234");         // "hello1234"
    System.out.println(s + (12*100 + 34));  // "hello1234"
    System.out.println(s + 12*100 + 34);    // "hello120034" (why?)
    System.out.println("The value of x is " + x);  // will work for any x
    System.out.println("System.out = " + System.out);
                // "System.out = java.io.PrintStream@80455198"
    String numbers = "";
    for (int i=0; i<5; i++)
        numbers += " " + i; // Inefficient reallocation, use StringBuffer instead!
    System.out.println(numbers);            // " 0 1 2 3 4"

Strings have lots of other useful operations:

    String s = "whatever", t = "whatnow";
    s.charAt(0);            // 'w'
    s.charAt(3);            // 't'
    t.substring(4);         // "now" (positions 4 through the end)
    t.substring(4,6);       // "no"  (positions 4 and 5, but not 6)
    s.substring(0,4);       // "what" (positions 0 through 3)
    t.substring(0,4);       // "what"
    s.compareTo(t);         // a value less than zero
                            // s precedes t in "lexicographic"
                            // (dictionary) order
    t.compareTo(s);         // a value greater than zero (t follows s)
    t.compareTo("whatnow"); // zero
    t.substring(0,4) == s.substring(0,4);
                            // false (they are different String objects)
    t.substring(0,4).equals(s.substring(0,4));
                            // true (but they are both equal to "what")
    t.indexOf('w');         // 0
    t.indexOf('t');         // 3
    t.indexOf("now");       // 4
    t.lastIndexOf('w');     // 6
    t.endsWith("now");      // true

and more.

You can't modify a string, but you can make a string variable point to a new string (as in numbers += " " + i;). This involves copying the old string value into a buffer, concatenating the new strings, and creating a new String object from the buffer, which is inefficient and should be avoided except for very limited concatenations. See StringBuffer if you want a string you can scribble on.

Constructors and Overloading

A constructor is like in C++: a method with the same name as the class. If a constructor has arguments, you supply corresponding values when using new. Even if it has no arguments, you still need the parentheses (unlike C++). There can be multiple constructors, with different numbers or types of arguments. The same is true for other methods. This is called overloading. Unlike C++, you cannot overload operators. The operator `+' is overloaded for strings and (various kinds of) numbers, but user-defined overloading is not allowed.

    class Pair {
        int x, y;
        Pair(int u, int v) {
            x = u;  // the same as this.x = u
            y = v;
        }
        Pair(int x) {
            this.x = x;  // not the same as x = x!
            y = 0;
        }
        Pair() {
            x = 0;
            y = 0;
        }
    }
    class Test {
        public static void main(String[] argv) {
            Pair p1 = new Pair(3,4);
            Pair p2 = new Pair();  // same as new Pair(0,0)
            Pair p3 = new Pair;    // error!
        }
    }

NB: The bodies of the methods have to be defined in line right after their headers as shown above. You have to write

    class Foo {
        double square(double d) { return d*d; }
    };

rather than

    class Foo {
        double square(double);
    };
    double Foo::square(double d) { return d*d; }
        // ok in C++ but not in Java

Inheritance, Interfaces, and Casts

In C++, when we write

    class Derived : public Base { ... }

we mean two things:

The first of these is called interface inheritance or subtyping and the second is called method inheritance. In Java, they are specified differently.

Method inheritance is specified with the keyword extends.

    class Base {
        int f() { /* ... */ }
        void g(int x) { /* ... */ }
    }
    class Derived extends Base {
        void g(int x) { /* ... */ }
        double h() { /* ... */ }
    }

Class Derived has three methods: f, g, and h. The method Derived.f() is implemented in the same way (the same executable code) as Base.f(), but Derived.g() overrides the implementation of Base.g(). We call Base the super class of Derived and Derived a subclass of Base. Every class (with one exception) has exactly one super class (single inheritance). If you leave out the extends specification, Java treats it like ``extends Object''. The primordial class Object is the lone exception -- it does not extend anything. All other classes extend Object either directly or indirectly. Object has a method toString, so every class has a method toString; either it inherits the method from its super class or it overrides it.

Interface inheritance is specified with implements. A class implements an Interface, which is like a class, except that the methods don't have bodies. Two examples are given by the built-in interfaces Runnable and Enumeration.

    interface Runnable {
        void run();
    }
    interface Enumeration {
        Object nextElement();
        boolean hasMoreElements();
    }

An object which is declared to be Runnable must have a method named run that is public and has no arguments or results. To be an Enumeration, a class has to have a public method nextElement() that returns an Object and a public method hasMoreElements that returns a boolean. A class that claims to implement these interfaces has to either inherit implementations of these methods (via extends) or define them itself.

    class Words extends StringTokenizer implements Enumeration, Runnable {
        public void run() {
            while (true) {
                String s = nextToken();
                if (s == null) {
                    return;
                }
                System.out.println(s);
            }
        }
        Words(String s) {
            super(s);
            // perhaps do something else with s as well
        }
    }

The class Words needs methods run, hasMoreElements, and nextElement to meet its promise to implement interfaces Runnable and Enumeration. It inherits implementations of hasMoreElements and nextElement from StringTokenizer , but it has to give its own implementation of run. The implements clause tells users of the class what they can expect from it. If w is an instance of Words, I know I can write

    w.run();

or

    if (w.hasMoreElements()) ...

A class can only extend one class, but it can implement any number of interfaces.

By the way, constructors are not inherited. The call super(s) in class Words calls the constructor of StringTokenizer that takes one String argument. If you don't explicitly call super, Java automatically calls the super class constructor with no arguments (such a constructor must exist in this case). Note the call nextToken() in Words.run, which is short for this.nextToken(). Since this is an instance of Words, it has a nextToken method -- the one it inherited from StringTokenizer.

A cast in Java looks just like a cast in C++: It is a type name in parentheses preceding an expression. We have already seen an example of a cast used to convert between primitive types. A cast can also be used to convert an object reference to a reference to a super class or subclass. For example,

    Words w = new Words("this is a test");
    Object o = w.nextElement();
    String s = (String)o;
    System.out.println("The first word has length " + s.length());

We know that w.nextElement() is ok, since Words implements the interface Enumeration, but all that tells us is that the value returned has type Object. We cannot call o.length() because class Object does not have a length method. In this case, however, we know that o is not just any kind of Object, but a String in particular. Thus we cast o to type String. Note that this does not convert the object itself; it only tells the compiler that even though all we had was a pointer of type Object, the pointer in fact already points to something of type String. Java always maintains full type information even at runtime, so if we were wrong about the type of o – if we lied to the compiler – we are guaranteed get a run-time error. If you are not sure of the type of an object, you can test it with instanceof (note the lower case `o'), or find out more about it with the method Object.getClass():

    if (o instanceof String) {
        n = ((String)o).length();
    } else {
        System.err.println("Bad type " + o.getClass().getName());
    }

Exceptions

A Java program should never ``core dump,'' no matter how buggy it is. If the compiler excepts it and something goes wrong at run time, Java throws an exception. By default, an exception causes the program to terminate with an error message, but you can also catch an exception.

    try {
        // ...
        foo.bar();
        // ...
        a[i] = 17;
        // ...
    }
    catch (IndexOutOfBoundsException e) {
        System.err.println("Oops: " + e);
        e.printStackTrace();
    }

The try statement says you're interested in catching exceptions. The catch clause (which can only appear after a try) says what to do if an IndexOutOfBoundsException occurs anywhere in the try clause. In this case, we print an error message. The toString() method of an exception generates a string containing information about what went wrong. If you call the printStackTrace() method instead, a stack trace will be printed, containing information about the method which caused this error to occur, the method which called that method, and so on.

Because we caught this exception, it will not terminate the program. If some other kind of exception occurs (such as divide by zero), the exception will be thrown back to the caller of this function and if that function doesn't catch it, it will be thrown to that function's caller, and so on back to the main function, where it will terminate the program if it isn't caught. Similarly, if the function foo.bar throws an IndexOutOfBoundsException and doesn't catch it, we will catch it here.

The catch clause actually catches IndexOutOfBoundsException or any of its subclasses, including ArrayIndexOutOfBoundsException , StringIndexOutOfBoundsException , and others. An Exception is just another kind of object, and the same rules for inheritance hold for exceptions as any other kind of class.

You can define and throw your own exceptions.

    class SyntaxError extends Exception {
        int lineNumber;
        SyntaxError(String reason, int line) {
            super(reason);
            lineNumber = line;
        }
        public String toString() {
            return "Syntax error on line " + lineNumber + ": " + getMessage();
        }
    }
    class SomeOtherClass {
        public void parse(String line) throws SyntaxError {
            // ...
            if (...)
                throw new SyntaxError("missing comma", currentLine);
            //...
        }
        public void parseFile(String fname) {
            //...
            try {
                // ...
                nextLine = in.readLine();
                parse(nextLine);
                // ...
            }
            catch (SyntaxError e) {
                System.err.println(e);
            }
        }
    }

Each function must declare in its header (with the keyword throws) all the exceptions that may be thrown by it, either directly or by any function it calls. It doesn't have to declare exceptions it catches, though. Some exceptions, such as IndexOutOfBoundsException, are so common that Java makes an exception for them (sorry about that pun) and doesn't require that they be declared. This rule applies to RuntimeException and its subclasses. You should almost never define new subclasses of RuntimeException.

There can be several catch clauses at the end of a try statement, to catch various kinds of exceptions. The first one that ``matches'' the exception (i.e., is a super class of it) is executed. You can also add a finally clause, which will always be executed, no matter how the program leaves the try clause (whether by falling through the bottom, executing a return, break, or continue, or throwing an exception).

Threads

Java lets you do several things at once by using threads. If your computer has more than one CPU, it may actually run two or more threads simultaneously. Otherwise, it will switch back and forth among the threads at times that are unpredictable unless you take special precautions to control it.

There are two different ways to create threads. I will only describe one of them here.

    Thread t = new Thread(command); // 
    t.start();  // t start running command, but we don't wait for it to finish
    // ... do something else (perhaps start other threads?)
    // ... later:
    t.join();  // wait for t to finish running command

The constructor for the built-in class Thread takes one argument, which can be of any class that implements the Runnable interface described earlier. Because the object must be of a class that implements Runnable, it is guaranteed to have a method called run. Once the new thread has actually been created, which involves a certain amount of low-level operations, the way the thread actually ``runs'' the specified command is simply by calling its run() method. It's as simple as that!

    class Command implements Runnable {
        String theCommand;
        Command(String c) {
            theCommand = c;
        }
        public void run() {
            // Do what the command says to do
        }
    }

In many cases, you will need to synchronize threads with each other. There are two reasons why you need to do this: to prevent threads from interferring with each other, and to allow them to cooperate. You use synchronized methods to prevent interference, and the built-in methods Object.wait() , Object.notify() , Object.notifyAll() , and Thread.yield() to support cooperation.

Any method can be preceded by the word synchronized (as well as public, static, etc.). The rule is:

No two threads may be executing synchronized methods of the same object at the same time.

The Java system enforces this rule by associating a monitor lock with each object. When a thread calls a synchronized method of an object, it tries to grab the object's monitor lock. If another thread is holding the lock, it waits until that thread releases it. A thread releases the monitor lock when it leaves the synchronized method. If one synchronized method of a calls contains a call to another, a thread may have the same lock ``multiple times.'' Java keeps track of that correctly. For example,

    class C {
        public synchronized void f() {
            // ...
            g();
            // ...
        }
        public synchronized void g() { /* ... */ }
    }

If a thread calls C.g() ``from the outside'', it grabs the lock before executing the body of g() and releases it when done. If it calls C.f(), it grabs the lock on entry to f(), calls g() without waiting, and only releases the lock on returning from f().

Sometimes a thread needs to wait for another thread to do something before it can continue. The methods wait() and notify(), which are defined in class Object and thus inherited by all classes, are made for this purpose. They can only be called from within synchronized methods. A call to wait() releases the monitor lock and puts the calling thread to sleep (i.e., it stops running). A subsequent call to notify on the same object wakes up a sleeping thread and lets it start running again. If more than one thread is sleeping, one is chosen arbitrarily. If no threads are sleeping in this object, notify() does nothing. The awakened thread has to wait for the monitor lock before it starts; it competes on an equal basis with other threads trying to get into the monitor. The method notifyAll is similar, but wakes up all threads sleeping in the object.

    class Buffer {
        private Queue q;
        public synchronized void put(Object o) {
            q.enqueue(o);
            notify();
        }
        public synchronized Object get() {
            while (q.isEmpty())
                wait();
            return q.dequeue();
        }
    }

This class solves the so-call ``producer-consumer'' problem (it assumes the Queue class has been defined elsewhere). ``Producer'' threads somehow create objects and put them into the buffer by calling Buffer.put(), while ``consumer'' threads remove objects from the buffer (using Buffer.get()) and do something with them. The problem is that a consumer thread may call Buffer.get() only to discover that the queue is empty. By calling wait() it releases the monitor lock and goes to sleep so that producer threads can call put() to add more objects. Each time a producer adds an object, it calls notify() just in case there is some consumer waiting for an object.

This example is not correct as it stands (and the Java compiler will reject it). The wait() method can throw an InterruptedException exception, so the get() method must either catch it or declare that it throws InterruptedException as well. The simplest solution is just to catch the exception and ignore it:

    class Buffer {
        private Queue q;
        public synchronized void put(Object o) {
            q.enqueue(o);
            notify();
        }
        public synchronized Object get() {
            while (q.isEmpty()) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return q.dequeue();
        }
    }

The method printStackTrace() prints some information about the exception, including the line number where it happened. It is a handy thing to put in a catch clause if you don't know what else to put there. Never use an empty catch clause. If you violate this rule, you will live to regret it!

There is also a version of Object.wait() that takes an integer parameter. The call wait(n) will return after n milliseconds if nobody wakes up the thread with notify or notifyAll sooner.

You may wonder why Buffer.get() uses while (q.isEmpty()) rather than if (q.isEmpty()). In this particular case, either would work. However, in more complicated situations, a sleeping thread might be awakened for the ``wrong'' reason. Thus it is always a good idea when you wake up to recheck the condition that made to decide to go to sleep before you continue.

Identity and State

Two of the most important aspects of an object are its identity and its state.

The state of the object is essentially determined by the values of its fields (data members): the state of a Pair object is determined by the values of its two members x and y. If two Pair objects both have x=7 and y=4, the objects have the same state. Similarly, the state of a String object is determined by the sequence of characters that make up the string. If two String objects both contain the characters "h" and "i" (and nothing else), they have the same state. This concept of state, is important enough that we want to have a standard method for comparing two objects with regard to state equality. In Java, this method is called equals() and is declared as follows (example from the Pair class):

public boolean equals(Object other) {
    if (this.getClass() == other.getClass()) {
        // OK, the other object is also a Pair so it might be equal to this one
        Pair p2 = (Pair) other; // Safe to cast to Pair
        if (this.x != other.x) return false; // Different x, can't be equal
        if (this.y != other.y) return false; // Different y, can't be equal
        return true; // Nothing left to compare, must have equal state!
    } else {
        // The other thing isn't even a Pair!  Can't be equal.
        return false;
    }
}

In C++, state equality is commonly implemented using the "==" comparison operator, which can be overloaded – every class can provide its own implementation of "==". Java does not allow operator overloading (apart for one special case for string concatenation), and reserves the "==" operator (and its negated version "!=") for identity comparisons. Every object that you ever allocate using the new operator has a unique identity; it might help if you think of the identity as being associated with the memory location where the object is stored. If one Pair object is stored at memory location 0x01073210 and one is stored at location 0x01074428, they are clearly two separate objects with two separate identities – even if their x and y fields would happen to have the same values at this time.

In other words, p1 == p2 always determines whether the variables or fields p1 and p2 point to the same object in memory, while p1.equals(p2) should determine whether p1 and p2 have equal state – but this depends on whether you have implemented the equals() method, and on whether you have done this correctly. If you have not implemented the method, a default implementation will be inherited from the Object class, but this implementation may not work as you would expect it to: It simply compares identity using the "==" operator, because this is the best you can do in a default implementation which has no knowledge of the class you wrote!

One more point still needs to be discussed here. In the example implementation of equals(), we used the "!=" operator to compare the fields of the Pair class. This is necessary because the fields (x and y) are primitive fields, which do not support methods such as equals(). If you have object fields, you should almost always use equals() to compare them. For example, if you had a String field called "name", which contains the name of the pair, then Pair.equals() should test whether this.name.equals(p2.name), not whether this.name == p2.name.

Other Goodies

The library of pre-defined classes has several other handy tools. See the online manual , particularly java.lang and java.util for more details.

Integer, Character, etc.

Java makes a big distinction between values (integers, characters, etc.) and objects. Sometimes you need an object when you have a value (the next paragraph has an example). The classes Integer, Character, etc. serve as convenient wrappers for this purpose. For example, Integer i = new Integer(3) creates a version of the number 3 wrapped up as an object. The value can be retrieved as i.intValue. These classes also serve as convenient places to define utility functions for manipulating value of the given types, often as static methods or defined constants.

    int i = Integer.MAX_VALUE;          // 2147483648, the largest possible int
    int i = Integer.parseInt("123");    // the int value 123
    String s = Integer.toHexString(123);// "7b" (123 in hex)
    double x = Double.parseDouble("123e-2");
                                        // the double value 1.23
    Character.isDigit('3')              // true
    Character.isUpperCase('a')          // false
    Character.toUpperCase('a')          // 'A'

ArrayList

An ArrayList is like an array, but it grows as necessary to allow you to add as many elements as you like. Beginning with Java 5, ArrayLists (and other types) can be parameterized, providing a functionality similar (but definitely not identical) to that of templates in C++. ArrayList implements the interface List, and it is always a good idea to declare lists to be of this type rather than requiring an ArrayList – this way, it is much easier to switch to another implementation of List, such as LinkedList, at a later date.

    List<Integer> v = new ArrayList();     // an empty list
    for (int i=0; i<100; i++)
        v.add(new Integer(i));
    // now it contains 100 Integer objects
    // print their squares
    for (int i=0; i<100; i++) {
        Integer member = v.get(i);
        int n = member.intValue();
        System.out.println(n*n);
    }
    // another way to do that
    for (Iterator<Integer> i = v.iterator(); i.hasNext(); ) {
        int n = i.next().intValue();
        System.out.println(n*n);
    }
    v.set(5, "hello");           // Compile-time error! Only Integer allowed
    v.add(6, new Integer(42));   // set v[6] = 42 after first shifting
                                 // element v[7], v[8], ... to the right
                                 // to make room
    v.remove(3);                 // remove v[3] and shift v[4], ... to the
                                 // left to fill in the gap

Elements of a Vector must be objects, not values. That means you can put a String or an instance of a user-defined class into a Vector, but if you want to put an integer, floating-point number, or character into Vector, you have to wrap it:

    v.add(47);                   // WRONG!
    sum += v.get(i);             // WRONG!
    v.add(new Integer(47));      // right
    sum += v.get(i).intValue();  // right

(From Java 1.5, though, a new pair of language features called auto-boxing and auto-unboxing handles the conversion between primitive types and objects automatically. This way, the lines marked "WRONG" above actually do compile – but this is merely syntactic sugar; these lines result in approximately the same code as the lines marked "right", and the ArrayList still contains Integer objects, not int values.)

The class ArrayList is implemented using an ordinary array that is generally only partially filled. If ArrayList runs out of space, it allocates a bigger array and copies over the elements.

Don't forget to import java.util.ArrayList; or import java.util.*; .

Maps and Sets

The interface Map represents a table mapping keys to values. It is sort of like an array or ArrayList, except that the ``subscripts'' can be any objects, rather than non-negative integers. Since Map is an interface rather than a class you cannot create instances of it, but you can create instances of the class HashMap, which implements Map using a hash table, or the class TreeMap, which implements Map as a tree of ordered objects.

    Map table = new HashMap();        // an empty table
    table.put("seven", new Integer(7));   // key is the String "seven";
                                      // value is an Integer object
    table.put("seven", 7);            // WRONG! (7 is not an object)
    Object o = table.put("seven", new Double(7.0));
                                      // binds "seven" to a double object
                                      // and returns the previous value
    int n = ((Integer)o).intValue();  // n = 7
    table.containsKey("seven");       // true
    table.containsKey("twelve");      // false
    // print out the contents of the table
    for (Iterator i = table.keySet().iterator(); i.hasNext(); ) {
        Object key = i.next();
        System.out.println(key + " -> " + table.get(key));
    }
    o = table.get("seven");           // get current binding (a Double)
    o = table.remove("seven");        // get current binding and remove it
    table.clear();                    // remove all bindings

Sometimes, you only care whether a particular key is present, not what it's mapped to. You could always use the same object as a value (or use null), but it would be more efficient (and, more importantly, clearer) to use a Set.

    System.out.println("What are your favorite colors?");
    BufferedReader in =
        new BufferedReader(new InputStreamReader(System.in));
    Set favorites = new HashSet();
    try {
        for (;;) {
            String color = in.readLine();
            if (color == null) {
                break;
            }
            if (!favorites.add(color)) {
                System.out.println("you already told me that");
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    int n = favorites.size();
    if (n == 1) {
        System.out.println("your favorite color is:");
    } else {
        System.out.println("your " + n + " favorite colors are:");
    }
    for (Iterator i = favorites.iterator(); i.hasNext(); ) {
        System.out.println(i.next());
    }

StringTokenizer

A StringTokenizer is handy in breaking up a string into words separated by white space (or other separator characters). The following example is from the Java book:

    String str = "Gone, and forgotten";
    StringTokenizer tokens = new StringTokenizer(str, " ,");
    while (tokens.hasMoreTokens())
        System.out.println(tokens.nextToken());

It prints out

    Gone
    and
    forgotten

The second argument to the constructor is a String containing the characters that should be considered separators (in this case, space and comma). If it is omitted, it defaults to space, tab, return, and newline (the most common ``white-space'' characters).

There is a much more complicated class StreamTokenizer for breaking up an input stream into tokens. Many of its features seem to be designed to aid in parsing the Java langauge itself (which is not a surprise, considering that the Java compiler is written in Java).

The Preprocessor

The Java language does not include a preprocessor. Much of the complexity of C and C++ can be traced back to the preprocessor. How many times have you read a C++ function and then needed to trace a defined value or macro back through a hierarchy of headers in order to understand the function? The preprocessor brought a lot of flexibility to C and C++, but it also added artificial complexity.

Java's removal of the preprocessor means that you will need to unlearn a couple of old habits. For example, you will no longer be able to use typedef and #define. In Java, you would instead use classes and constants.

Erich Kaltofen's Java cheat sheet for C++ programmers

(by Erich Kaltofen, kaltofen@math.ncsu.edu; somewhat modified for this course)

C++ Java
assignment operator= cannot be user-defined for a class; the standard implementation of "=" assigns a pointer / reference and does not copy the contents of an object (see also reference types)
basic_string String and StringBuffer
bool boolean
char byte for 8-bit values, char for characters (16 bits)
const variables/data members final variables/fields are somewhat similar, but do not have exactly the same semantics
copy constructor no default copy constructor exists, and it is not needed as often in Java since one usually passes objects by reference; if one needs to explicitly make a copy of an object, one either implements such a constructor 'manually' or implements the interface Cloneable containing the method Object clone(), which can be an abstract (in C++ notion: virtual) method
data members fields, so-called instance variables (a term borrowed from Smalltalk)
delete does not exist; all unreferenced memory is garbage collected
derived classes subclasses; the keyword extends replaces C++'s colon.
destructors ~Class usually not used, since garbage collection automatically frees memory; the method protected void finalize() can be implemented for certain very special cases when certain cleanup should be done, and is executed automatically by the garbage collector before the object is free; note that you have no control over when the finalization method is executed and that the program is allowed to exit without calling all finalizers.
exceptions, try, catch, throw, std:exception same concept; Java adds a keyword throws that is used to declare the exceptions a method throws; the hierarchy of exceptions is rooted in java.lang.Exception; a finally block is introduced to contain all common clean-up code.
extern "C" functions native methods
functions do not exist; static methods (``class methods'') are used
#include does not exist; the paths to the files are known and can be made known in the CLASSPATH environment variable or in the configuration of an integrated development environment
input/output: istream& operator», ostream& operator« System.in and System.out are the streams; Java has number formatting tools in java.lang.Number and java.text.Format.NumberFormat
main(int argc, char* argv[]) public static void main(String [] args) within a public class
member functions methods
multiple inheritance does not exist; however, interfaces provide a weak form of multiple inheritance.
namespaces packages
namespace Namespace{...} package Package; which must appear as the first line in the file
nested (member, inner) classes Java 1.1 has static (``top-level'') and non-static (``member'') inner classes, as well as local classes and anonymous classes. Member classes can refer to the members of the outer class and to OuterClass.this; they cannot have the name of an outer class and cannot declare static members.
new Class(...) new Class(...), which returns a reference to the created object
NULL (the 0 pointer value) and the type void* null in Java is a keyword and represents an uninitialized reference
overloaded operators do not exist; however, methods can be overloaded. This may be a major shortcoming of Java, as one cannot revise old Java code by redefining the operators used (cf. MITMatlab)
passing arguments to base class constructor place the statement super(...); as the first statement in the subclass's constructor
public, private, protected modifiers similar as in C++; visibility of classes and nested classes can be also restricted; there are no friends, but within the same package protected members are visible
purely virtual member functions abstract methods; the enclosing class must also be declared abstract
reference types Type& all Java types except scalar primitive types are reference types; note that the method
void swap(T a, Tb) {T t; t = a; a = b; b = t;}
changes the values of the local variables (parameters) a and b but does nothing to the values sent in by the caller.
scope resolution, operator :: does not exist; methods must be defined inside the class declaration. If a base class field is to be explicitly referred, one uses typecasting: ((Baseclass)Variable).Member; a direct base class member can be referred to by super.Member; typecasting has no effect on methods (see virtual member functions).
static data members static fields, so-called class variables; they are accessed by Class.Field rather than the C++ Variable.Member; they can be initialized by =...; within the class definition and need not be declared outside like C++ static data members.
static member functions static methods, so-called class methods; they are defined within the class declaration, unlike in C++.
this this, which is a reference to the object and has the type of the class; note that the call this(...); as the first statement in a constructor is another use of the keyword "this", which invokes a constructor call for the matching argument types.
traits marker interfaces
type_id instanceof; this is an operator returning a boolean, not a ``type_info'' as in C++.
using namespace Package; import Package.*;
virtual member functions in Java, all methods use dynamic method lookup and therefore are be default virtual. There is no way to explicity call an overridden base class method, but overwriting can be prevented by declaring a method final.
wchar_t char
wide character stream wostream PrintWriter replaces PrintStream that cannot hold unicode; the constructor of PrintStream has been deprecated in Java 1.1, but System.out is not.

Java concepts missing in C++
abstract windows toolkit AWT standard library for building a GUI
concatenation of strings by + operator  
documentation comments can be processed (e.g., by javadoc) for automatic online documentation
final methods those cannot be overridden by a subclass
interfaces are used to denote abstract classes without any method of their own. They can have static final fields. One class can implement several interfaces, but it must implement the abstract methods of each interface.
reflection allows the inspection of a class (which arguments does which member take? etc.); this is critical for plug-and-play design, such as a Java bean
right shift operator with zero extension >>>  
serialization C++ requires the programmer to implement object serialization member functions
sockets  
threads  

C++ concepts missing in Java
const member functions do not exist; final methods cannot be overridden by subclasses
friend classes, functions do not exist; however, protected members and members with default protection are visible within the same package
goto is a reserved word in Java, but is not supported by the language; however break and continue statements can give a statement label
multiple inheritance virtual base classes seem unachievable by using interfaces; delegation can be used in many cases
new(Pointer) Type(...); Pointer->~Type(); this is C++'s explicit memory allocation mechanism. In Java, all memory is managed by the VM and garbage collection is automatic.
pointer types Type* do not exist; actually, since Java has only reference types, all variables are some kind of pointers and the = operator behaves like a pointer assignment
pointer to function, member not a serious restriction, as one may encapsulate a function in a function object