10-18-04 <T extends NonGenericType> and Class<T>
As I continue to study the mysteries of erasure-based generics in Java, I've
had a few small epiphanies about bounds.
- Bounds are necessary when you want to go beyond "holder" generics to
generics that perform operations upon objects of parameterized types.
- Bounds compensate for erasure. Since the type of a parameter is erased
within a generic definition, you cannot know what legal operations exist for
that type. By giving a bound, you only allow types satisfying that bound to pass
into the generic. You can then safely perform operations satisfying that bound
(and the compiler checks everything).
- When a generic with a bound is compiled, the parameterized type is
replaced everywhere by the first bound. Successive bounds are replaced by casts.
Because of the last point, the class Bounds:
class PipeFitting {
public void solder() {}
}
class FortyFive extends PipeFitting {}
class Ninety extends PipeFitting {}
class UJoint extends PipeFitting {}
public class Bounds<T extends PipeFitting> {
private T fitting;
public Bounds(T fitting) { set(fitting); }
public void set(T fitting) { this.fitting = fitting; }
public T get() { return fitting; }
public void solder() { fitting.solder(); }
public static void main(String[] args) {
Bounds<PipeFitting> pf =
new Bounds<PipeFitting>(new FortyFive());
}
}
Becomes, after erasure:
public class Bounds {
private PipeFitting fitting;
public Bounds(PipeFitting pipefitting) {
set(pipefitting);
}
public void set(PipeFitting pipefitting) {
fitting = pipefitting;
}
public PipeFitting get() {
return fitting;
}
public void solder() {
fitting.solder();
}
public static void main(java.lang.String args[]) {
Bounds bounds = new Bounds(new FortyFive());
}
}
I actually compiled it and ran it through JAD to produce this
output.
The important thing to note is that a class with a bound where the bound does
not involve a type parameter is no different from a class that is not generic,
but just uses arguments of that bound.
From this we could conclude that it never makes sense to create a generic
class if you are going to use bounds that do not involve a type parameter.
Although I discovered from
Neal Gafter's comment (search for "mea culpa") that we cannot necessarily
rely on the Java library sources to be good examples of coding style, they are
nonetheless the most abundant examples of generic programming around. So I still
think it's worth hunting through them to see what is done.
If I do a regular expression search for '<[A-Z]+ extends',
there were lots and lots of generics of the form:
<T extends Foo> returnType f(Class<T> token) {
// ...
}
A non-parameterized type is used in the bound, but the only reason for that
type is so that a Class<T> token can be passed in. This
"class token" idiom is very common in the Java libraries (sometimes for partly
reversing the effect of erasure by passing in the exact class type).
Class<T> is new in Java 5. What does it mean? Previously,
if we wanted a reference to a class object, we used Class. This is
analogous to the way that Object points to any kind of object
a Class points to any kind of class object. So an initial
guess as to the meaning of Class<T> is that it would allow
you to point to a T.class or any subtype of T.class.
But that doesn't work:
Class<Number> cn = Integer.class; // Won't compile.
Class<T> will only point to the exact type of
T, not a subclass. This fits with the reason for the existence of
wildcards, and we are in fact able to use wildcards here:
Class<? extends Number> cn = Integer.class; // Compiles.
And we can also revert to the meaning "points to any class" using an unbounded
wildcard:
Class<?> cn = Integer.class; // OK.
So Class<T> is like a single-object generic holder class
that only holds class references. When we need to pass class tokens,
Class allows us to pass a reference to any class, whereas
Class<T> performs a compile-time check to ensure that a
reference of a particular class object is passed in. So we have compile-time
safety for passing in class tokens. The idiom looks like this (for a generic
method):
public static <T extends Foo> void f(Class<T> token) {}
This restricts the method so that it can only be used with Foo
or a subclass, and it forces the token to be of the exact type of the parameter.
Actually, in the case of a generic method, type inference takes over and the
method is instantiated for whatever type you pass in as the class object. That
is, you just say f(Foo.class) and the parameter type is inferred
for you.
The key thing I'm trying to point out here is that
Class<T> seems to be used in a very narrow, idiomatic way;
together with <T extends Foo> (where Foo does
not involve a type parameter, but is a specific class): simply to provide
compile-time type checking for passing in a class token. And I'm not attacking
static type checking here, I'm simply pointing out the idiom so that you can
recognize what it means when you see it. (And I'm putting it up as a conjecture
in this article to see if anyone has any other observations or interpretations).
Consider an example from the pre-Java 5 and post Java 5 libraries. Here is
AWTEventMulticaster.getListeners() in Java 1.4:
public static EventListener[]
getListeners(EventListener l, Class listenerType) {
int n = getListenerCount(l, listenerType);
EventListener[] result =
(EventListener[])Array.newInstance(listenerType, n);
populateListenerArray(result, l, 0);
return result;
}
Here, I can only pass in an EventListener (or subtype), but I
can pass in absolutely any kind of Class object.
Here's the same code in Java 5:
public static <T extends EventListener> T[]
getListeners(EventListener l, Class<T> listenerType) {
int n = getListenerCount(l, listenerType);
T[] result = (T[])Array.newInstance(listenerType, n);
populateListenerArray(result, l, 0);
return result;
}
When you call this method, listenerType can be the class
EventListener or a subclass, and the return type T[]
will be of the same class as listenerType. So the idiom of
<T extends NonGenericType> coupled with
Class<T> (which seems to be the only appropriate way to apply
<T extends NonGenericType>) is not a way to apply a method
across more types, but instead a way to perform more precise static type
checking.
In searching through the standard library code base, I also found quite a
number of Class<? extends Foo>, for cases where the method
wanted to allow all the subclass objects as well. Again, this provides better
static type checking than just using a Class reference.