CSE 341 -- Java Generics

Java's type rules

A brief recap: remember Java's rules for type compatibility from 142/143. If B is a subtype of A, then we can use an object of type B anywhere that we expect something of type A. For example:
Object a;
Point p = new Point(10,20);
Point q;
a = p;  // legal since a point is a kind of an object
q = a;  // NOT LEGAL (since the compiler only knows that a is of type Object)
q = (Point) a;  // legal (since we put in a runtime type check using a cast)
Similarly, if we had a method that expects an object as a parameter, we can pass it a Point, a String, or any other object.

Parametric Polymorphism

Recall that ML's type system allows one to declare types such as
('a -> 'b) -> 'a list -> 'b list
(the type of the map function).

Up until recently, programmers couldn't declare types of this sort in Java. This led to less precise type declarations, less machine-checkable documentation, and more runtime casts. For example, the built-in collection classes in Java (Set, ArrayList, etc) all had Object as the type of the contents -- you couldn't declare that x is a set of strings, or a set of points.

For example, consider the following Java code fragment in pre-version 1.5 Java (or version 5, depending on how you count).

ArrayList a = new ArrayList();
Point p = new Point(10,20);
Point q;
// add the point p to the arraylist
a.add(1,p);
// now get the point out of the arraylist and assign it to q
// bleah!  I know perfectly well that I've only put points into a, but
// Java's type system says that the type of a.get(i) is Object.  So I 
// need to put in a cast and have it do a runtime check.
q = (Point) a.get(1);

Java Arrays

Java arrays have always been an exception to this: rather than just being able to declare arrays of Object, one can declare an array of ints, Points, etc. (Note that this works both for primitive types and for objects.) Java checks that you are putting the right kind of object or type into the array, and knows that you're getting the same type out. For example:
Point[] a = new Point[10]; 
Point p = new Point(10,20); 
Point q; 
a[0] = p; 
q = a[0]; // no cast needed - we know a[0] is a Point 
Java regards an array of points as a subtype of array of objects ... which sounds reasonable, but alas isn't true! So Java's type checking for arrays is unsound! However, the Java designers were well aware of this problem, and so Java includes a runtime check (so you get an exception, not a crash). Consider:
import java.awt.Point;

class ArrayException {
    public static void main(String[] args) {
	String[] s;
	s = new String[10];
	s[0] = "hi there";
	test(s);
    }

    public static void test(Object[] a) {
	System.out.println("in test - before storing into a");
	a[1] = new Point(10,20);
	System.out.println("in test - after storing into a");
    }
}
This gives the following result:
in test - before storing into a
Exception in thread "main" java.lang.ArrayStoreException
        at ArrayException.test(ArrayException.java:16)
        at ArrayException.main(ArrayException.java:11)
So there is even a specialized kind of exception for this situation.

The technical term for the kind of type checking rule that Java uses for arrays (and that has this unsoundness problem) is covariant typing.

Java Generics

Integrating parametric polymorphism and inheritance is not easy, and there has been academic work in this area for many years, as well as various experimental languages. Java version 5.0 includes support for generics.

Code examples

OldLinkedListExample.java - Linked list example without generics
// CSE 341, Autumn 2005
// Example of collection code written without generics.
// Make a linked list, add a couple of integers to it,
// get an iterator over the list, and get the integers out.
// Since the type of the LinkedList and of the iterator 
// doesn't use a parameterized type, we need to use a cast 
// when extracting the integers.

import java.util.LinkedList;
import java.util.Iterator;

class OldLinkedListExample {

    public static void main(String[] args) {
	LinkedList myIntList = new LinkedList(); 
	myIntList.add(new Integer(3)); 
	myIntList.add(new Integer(4)); 
	Iterator i = myIntList.iterator();
	Integer x = (Integer) i.next();  
	Integer y = (Integer) i.next();
	System.out.println(x);
	System.out.println(y);
    }
}

LinkedListExample.java - Linked list of integers with generics
// CSE 341, Autumn 2005
// Simple example of using Java generics.
// Make a linked list of integers, add a couple of integers to it,
// get an iterator over the list, and get the integers out.
// Notice that the LinkedList uses a parameterized type, as does the
// iterator.

import java.util.LinkedList;
import java.util.Iterator;

class LinkedListExample {

    public static void main(String[] args) {
	LinkedList<Integer> myIntList = new LinkedList<Integer>(); 
	myIntList.add(new Integer(3)); 
	myIntList.add(new Integer(4)); 
	Iterator<Integer> i = myIntList.iterator();
	Integer x = i.next();  
	Integer y = i.next();
	System.out.println(x);
	System.out.println(y);
    }
}

ObjectLinkedListExample.java - Linked list of objects with generics
// CSE 341, Autumn 2005
// Example of collection code written without generics.
// Make a linked list, add an integer and a point to it.  Then
// get an iterator over the list, and get the integer and point out.
// Use casts.

import java.util.LinkedList;
import java.util.Iterator;
import java.awt.Point;

class ObjectLinkedListExample {
    public static void main(String[] args) {
	LinkedList<Object> list = new LinkedList<Object>(); 
	list.add(new Integer(3)); 
	list.add(new Point(10,20));
	Iterator<Object> itr = list.iterator();
	// living dangerously -- we happen to know that the first thing
	// in the list is an integer and the second is a point.
	Integer i = (Integer) itr.next();
	Point p = (Point) itr.next();
	System.out.println(i);
	System.out.println(p);
    }
}

Wild1.java - simple example of using a wildcard
// CSE 341, Autumn 2005
// Simple example of using a wildcard.
// Make a linked list of ?
// The only value we can add to it is null (since this is a legal value
// of any type).  We can get an iterator for the linked list, but we
// don't know what kind of types it produces.  (But they must be objects.)

import java.util.LinkedList;
import java.util.Iterator;

class Wild1 {

    public static void main(String[] args) {
	LinkedList<?> s = new LinkedList<Integer>(); 
	s.add(null);
	Iterator<?> x = s.iterator();
	Object o = x.next();
	System.out.println(o);

	// note that LinkedList<?> is NOT the same type as LinkedList<Object>
	// here are some statements that wouldn't compile:
	// s.add(new Integer(3)); 
	// Iterator<Object> x = s.iterator();
    }
}

Wild2.java - method with a wildcard
// CSE 341, Autumn 2005
// Example of using a wildcard.  Define a method that takes 
// a linked list of ? as a parameter.

import java.util.LinkedList;
import java.util.Iterator;
import java.awt.Point;

class Wild2 {

    // note that we couldn't declare the parameter as LinkedList<Object> !

    public static void printAll(LinkedList<?> s) {
	for (Object e : s) {
	    System.out.println(e);
	}
    }

    // another version using an iterator
    public static void printAll2(LinkedList<?> s) {
	Iterator<?> it = s.iterator();
	while (it.hasNext()) {
	    System.out.println(it.next());
	}
    }

    public static void main(String[] args) {
	LinkedList<Integer> ilist = new LinkedList<Integer>(); 
	ilist.add(new Integer(3)); 
	ilist.add(new Integer(5)); 
	ilist.add(null);
	printAll(ilist);
	printAll2(ilist);

	LinkedList<Point> plist = new LinkedList<Point>(); 
	plist.add(new Point(10,20)); 
	printAll(plist);
	printAll2(plist);
    }
}

Wild3.java - method with a bounded wildcard
// CSE 341, Autumn 2005
// Example of using a bounded wildcard.  Define a method that finds
// the maximum width of a list of rectangular shapes

import java.util.LinkedList;
import java.util.Iterator;
import java.awt.Point;
import java.awt.geom.RectangularShape;
import java.awt.geom.Rectangle2D;
import java.awt.geom.Ellipse2D;


class Wild3 {

    // version 1 of maxWidth -- note that we need a cast to get 
    // rectangular shapes out of the list
    public static double maxWidth1 (LinkedList<?> s) {
	double m = 0.0;
	for (Object e : s) {
	    RectangularShape r = (RectangularShape) e;
	    if (r.getWidth() > m) {
		m = r.getWidth();
	    }
	}
	return m;
    }

    // version 2 of maxWidth, using a bounded wildcard
    public static double maxWidth2 (LinkedList<? extends RectangularShape> s) {
	double m = 0.0;
	for (RectangularShape e : s) {
	    // no cast needed!
	    RectangularShape r = e;
	    if (r.getWidth() > m) {
		m = r.getWidth();
	    }
	}
	return m;
    }

    public static void main(String[] args) {
	// demonstrate the use of the maxWidth method by calling it
	// with lists of different kinds of rectangular shapes
	// first make a linked list of rectangles
	LinkedList<Rectangle2D> rlist = 
	    new LinkedList<Rectangle2D>(); 
	rlist.add(new Rectangle2D.Double(0.0, 0.0, 50.0, 100.0)); 
	rlist.add(new Rectangle2D.Double(0.0, 0.0, 12.0, 18.0)); 
	double ans1 = maxWidth1(rlist);
	System.out.println(ans1);	
	double ans2 = maxWidth2(rlist);
	System.out.println(ans2);	
	// now do the same thing, but with ellipses
	LinkedList<Ellipse2D> elist = 
	    new LinkedList<Ellipse2D>(); 
	elist.add(new Ellipse2D.Double(0.0, 0.0, 20.0, 30.0));
	elist.add(new Ellipse2D.Double(0.0, 0.0, 24.0, 30.0));
	double ans1e = maxWidth1(elist);
	System.out.println(ans1e);	
	double ans2e = maxWidth2(elist);
	System.out.println(ans2e);	


    }
}
MyArray.java - an array-like class (written with generics)
// An array-like class (written with generics)

import java.util.Iterator;
import java.util.NoSuchElementException;
import java.awt.Point;

public class MyArray<E> {
    /* internal array to actually hold the data */
    private E [] internalArray;

    // the constructor
    public MyArray (int n) {
	internalArray = (E[]) new Object[n];
	// unfortunately this doesn't work due to current Java limitations:
	// internalArray = new E[n];
    }

    public int length() {
	return internalArray.length;
    }

    // get an element
    public E at(int i) {
	return internalArray[i];
    }

    // set an element
    public void set(int i, E value) {
	internalArray[i] = value;
    }

    /**
     * inner class for the iterator
     */
    class MyIterator implements Iterator<E> {
	int index;
	MyIterator() {
	    index = 0;
	}
	public E next() {
	    E temp;
	    if (!hasNext())
		throw new NoSuchElementException("no next element available");
	    temp = internalArray[index];
	    index++;
	    return temp;
	}
	public boolean hasNext() {
	    return index < internalArray.length;
	}
	public void remove() {
	    /* this is an optional operation - we don't support it */
	    throw new UnsupportedOperationException("remove operation not supported");
	}
    }
    
    /**
     * return an iterator for this array
     */
    public Iterator<E> iterator() {
	return new MyIterator();
    }

    public static void main(String[] args) {
	MyArray<Integer> a = new MyArray<Integer>(2);
	a.set(0, new Integer(50));
	a.set(1, new Integer(100));
	Iterator<Integer> it = a.iterator();
	while(it.hasNext()) {
	    System.out.println(it.next());
	}
    }
}

Code examples (as separate files):

References