CIT 594 Coping with Generics
Spring 2012, David Matuszek

Generics are a Good Thing™, and someday I may even learn to like them. Meanwhile...

References

Generics in the Java Programming Language, by Gilad Bracha
http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf
Sun's tutorial (as a PDF file)--about 22 pages long.
Angelika Langer's Generics FAQ
http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html
If it's not here, it probably doesn't exist.
Java Generics Without the Pain, Parts 1 - 4
http://www-128.ibm.com/developerworks/java/library/j-djc02113.html
http://www-128.ibm.com/developerworks/java/library/j-djc03113.html
http://www-128.ibm.com/developerworks/library/j-djc04093.html
http://www-128.ibm.com/developerworks/java/library/j-djc05133.html
This series of articles is good, but old and incomplete.

Why generics?

Generics improve type safety.

Before Java 5, when you put something into a collection, you had to keep track of what type it was, so you could cast it back to that type when you got it out. If you made a mistake, and you happened to execute the code with the mistake, you got a ClassCastException at runtime. Of course, that particular bit of code might never be executed until it was in the customer's hands.

With generics, a mistake will give you a syntax error, which is far more desirable than a runtime error.

As a small bonus, you usually no longer have to cast when you get something out of a collection.

Using collections

Java's collections (in java.util.*) are pretty straightforward to use:

Create and use a collection of a given type:

    ArrayList list0 = new ArrayList(); // Pre-Java 5 (no generics) works, with warning 

    ArrayList<String> list1 = new ArrayList<String>(); // An ArrayList of Strings

    list1.add("abc"); // OK because "abc" is a String
    list1.add(new Integer(5)); // Error
    String s = list1.get(0); // No cast needed

Create and use a collection with two or more type parameters:

    Map<String, ArrayList> map1 = new HashMap<String, ArrayList>();
    map1.put("abc", new ArrayList());
    map1.put("xyz", new ArrayList());
    ArrayList arrayList = map1.get("abc"); // No cast needed

Create and use a collection with nested type parameters:

    ArrayList<String> array = new ArrayList<String>();
    array.add("abc");

    Map<String, ArrayList<String>> map2 = 
        new HashMap<String, ArrayList<String>>();

    map2.put("xyz", array);
    String s = map2.get("xyz").get(0); // No cast needed

Create and use a collection that can hold any type of object:

    ArrayList<Object> list3 = new ArrayList<Object>();
    list3.add("abc");
    list3.add(5); // OK because "5" is autoboxed to "new Integer(5)"

Create and use a collection that can hold certain types of object:

    ArrayList<Number> list4 = new ArrayList<Number>();
    list4.add(5); // OK because class Integer extends class Number
    list4.add(3.1416); // OK because class Double extends class Number
    list4.add("abc"); // Error because String doesn't extend Number

You cannot create a collection of primitives:

    ArrayList<int> foobar; // Error because an int isn't an Object

Creating your own generic classes

Create a class that takes one or more types as parameters:

    class MyGen<T> { ... }
    class MyTwoGen<T, U> { ... }

Don't repeat the type in the constructors:

    public MyGen() { ... } // Correct
    public MyGen<T>() {...} // Error

Create instances of the class:

    MyGen<String> gen = new MyGen<String>();
    MyTwoGen<String, Integer> gen = new MyTwoGen<String, Integer>();

Declare variables of the type parameter:

    T something;
    T[] someThings;
    U somethingElse;
    ArrayList<T> listOfT = new ArrayList<T>();

You cannot create new objects of a type parameter:

    T something = new T(); // Error because constructors are unknown

Use the types as parameters to methods and constructors, and even as a return type:

    T myMethod(U param, T t1, T t2) {
        T t3 = (param == null ? t1 : t2);
        return t3;
    }

Create a class that takes a bounded type as a parameter:

    class Bounded<T extends Panel> {...}

    Bounded<Panel> xxx = new Bounded<Panel>(); // OK because a Panel is a Panel
    Bounded<Applet> yy = new Bounded<Applet>(); // OK because an Applet is a Panel
    Bounded<String> zz = new Bounded<String>(); // Illegal; Strings aren't Panels

Specify allowable method parameter types with wildcards:

Note: Here is the Java hierarchy used in these examples:

     java.lang.Object
        extended by java.awt.Component
           extended by java.awt.Container
              extended by java.awt.Panel
                 extended by java.applet.Applet
                    extended by javax.swing.JApplet
class Bounded<T extends Panel> {...}
With this method declaration:
void foo(Bounded<Applet> things) {...]
only <Applet> is a legal parameter:
foo(new Bounded<Container>()); // Not a legal Bounded parameter
foo(new Bounded<Panel>()); // Not an Applet
foo(new Bounded<Applet>()); // OK
foo(new Bounded<JApplet>())
// Not an Applet;

With this method declaration:
void bar(Bounded<? extends Applet> things) {...}
<Applet> and things that extend it are legal parameters:
bar(new Bounded<Container>()); // Not a legal Bounded parameter
bar(new Bounded<Panel>()); // Not an Applet

bar(new Bounded<Applet>()); // OK because it's an Applet
bar(new Bounded<JApplet>()); // OK because it's an Applet

With this method declaration:
void baz(Bounded<? super Applet> things) {...}
<Applet> and its superclasses (up to <Panel>) are legal parameters:
baz(new Bounded<Container>()); // Not a legal Bounded parameter
baz(new Bounded<Panel>()); // OK because Applet or superclass
baz(new Bounded<Applet>()); // OK because Applet or superclass
baz(new Bounded<JApplet>()); // Not a superclass of Applet

With this method declaration:
void boo(Bounded<?> things) {...}
Any types (up to <Panel>) are legal parameters:
boo(new Bounded<Container>()); // Not legal Bounded parameter
boo(new Bounded<Panel>()); // OK because legal Bounded parameter
boo(new Bounded<Applet>()); // OK because legal Bounded parameter
boo(new Bounded<JApplet>()); // OK because legal Bounded parameter

Other things that can be generic

Create methods that take type parameters:

    public <V> String print(V param) {
        return param.toString();
    }

    static public <T> Set<T> valuesOf(Tree<T> root) { ... }
    //             1      2                3
    // This method takes a type parameter <T> at (1) and a regular parameter
    // Tree<T> at (3), and returns a Set<T> at (2).

Create generic constructors:

class Something {
    String s;
    public <T> Something(T arg) {
        s = arg.toString();
    }
}

Create and implement generic interfaces:

interface Iface<T> {
    void work(T arg);
}
class Impl<U> implements Iface<U> {
    public void work(U arg) {
        System.out.println(arg);
    }
}

Things you can't do

In order to add generics to an existing language without breaking legacy code, a number of important compromises had to be made. Most of these are the result of erasure: The generic information is only used by the compiler, but is not present (erased) in the running code.

You cannot construct a new object of a parameterized type: T foo = new T();

You cannot create a new array of a parameterized type: T bar[] = new T[100];
...but wildcards are OK: Generics<?> ints[] = new Generics<?>[100];

You can't create an array of a specific parameterized type: Generics<Integer> ints = new Generics<Integer>[100];

You cannot ask about the generic type: list instanceof ArrayList<String>

Static members cannot refer to type parameters of the enclosing class: class X<T> { static T foo; }

A generic class cannot extend Throwable.