| CIT
594 Coping with Generics Spring 2006, David Matuszek |
Generics are a Good Thing, and someday I may even learn to like them. Meanwhile...
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.
Java's collections (in java.util.*) are pretty straightforward
to use:
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
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
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
ArrayList<Object> list3 = new ArrayList<Object>();
list3.add("abc");
list3.add(5); // OK because "5" is autoboxed to "new Integer(5)"
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
ArrayList<int> foobar; // Error because an int isn't an Object
class MyGen<T> { ... }
class MyTwoGen<T, U> { ... }
public MyGen() { ... } // Correct
public MyGen<T>() {...} // Error
MyGen<String> gen = new MyGen<String>();
MyTwoGen<String, Integer> gen = new MyTwoGen<String, Integer>();
T something;
T[] someThings;
U somethingElse;
ArrayList<T> listOfT = new ArrayList<T>();
T something = new T(); // Error because constructors are unknown
T myMethod(U param, T t1, T t2) {
T t3 = (param == null ? t1 : t2);
return t3;
}
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
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> {...}
void foo(Bounded<Applet>
things) {...]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;void bar(Bounded<? extends Applet>
things) {...}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 Appletvoid baz(Bounded<? super Applet>
things) {...}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 Appletvoid boo(Bounded<?> things)
{...}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 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).
class Something {
String s;
public <T> Something(T arg) {
s = arg.toString();
}
}
interface Iface<T> {
void work(T arg);
}
class Impl<U> implements Iface<U> {
public void work(U arg) {
System.out.println(arg);
}
}
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:
You cannot create a new array of a parameterized type:
...but wildcards are OK: Generics<?> ints[] = new Generics<?>[100];
Generics<Integer>
ints = new Generics<Integer>[100];
You cannot ask about the generic type:
Static members cannot refer to type parameters of the enclosing class:
A generic class cannot extend Throwable.