Why is Java's Class<T>
generic?
Here is a reasonably good summary of the advantages: http://www.j2ee.me/docs/books/tutorial/extra/generics/literals.html
Er, so you can use generic typed methods -
Class<Foo> klass;
Foo f = klass.newInstance();
Foo f = klass.cast(Object);
It permits you to create classes that handle objects from different. Think about the hell it would be if you have to write a list for each class present in your project. Instead of writing things like:
public class IntegerList {
...
public insert(int element);
...
}
and
public class StringList {
...
public insert(String element);
...
}
You can create:
public class<T> List {
...
public insert(T element);
...
}
and use it like:
List<int> intList = new List<int>();
intList.insert(1);
List<String> stringList = new List<String>();
stringList.insert("a");
There's a short mention of this in the Generics section of the 1.5 version of the language guide:
More surprisingly, class Class has been generified. Class literals now function as type tokens, providing both run-time and compile-time type information. This enables a style of static factories exemplified by the getAnnotation method in the new AnnotatedElement interface:
<T extends Annotation> T getAnnotation(Class<T> annotationType);
This is a generic method. It infers the value of its type parameter T from its argument, and returns an appropriate instance of T, as illustrated by the following snippet:
Author a = Othello.class.getAnnotation(Author.class);
Prior to generics, you would have had to cast the result to Author. Also you would have had no way to make the compiler check that the actual parameter represented a subclass of Annotation
It's a hack, designed as a workaround for the flawed design of Java generics, a language feature that was designed that way (using type erasure) for the sake of backward compatibility.
An example of it's use: the EnumSet
factory methods.
The implementation of EnumSet
requires access to some static fields of the enum
(to convert between references and integer IDs) but because of type erasure, this is not normally accessible at run-time.
Using a variant of one of Sun's examples:
import java.util.EnumSet;
enum Season { WINTER, SPRING, SUMMER, FALL }
class Main {
public static void main(String[] args) {
EnumSet<Season> seasons = EnumSet.noneOf(Season.class);
// ...
}
}
Why does EnumSet.noneOf()
require a Class<T>
object? Because when it queries its own type at run time, it only knows that it is an EnumSet
. Because of type erasure, the element type is not available at run-time.
Unless we pass it as a parameter that is. Which we do.
The type parameter T
in Class<T>
is there to ensure you can only insert enum values of the correct type.
If you look at the signature of the static method EnumSet.noneOf
, you will see that the element type of the returned set is unified with the type of the passed Class
:
static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType);
Substituting Season
for E
, we get:
static EnumSet<Season> noneOf(Class<Season> elementType);
Now there is only one instance of Class<Season>
, and that's Season.class
, so we have to pass that.
This means that the EnumSet
gets the correct value for the Season.values()
array, which will work for any value we add to the set (because seasons.add()
requires a Season
argument.)