I\'ve occasionally heard that with generics, Java didn\'t get it right. (nearest reference, here)
Pardon my inexperience, but what would have made them better?
Java doesn't enforce Generics at run time, only at compile time.
This means that you can do interesting things like adding the wrong types to generic Collections.
(1) leads to some very strange behaviour. The best example I can think of is. Assume:
public class MyClass<T> {
T getStuff() { ... }
List<String> getOtherStuff() { ... }
}
then declare two variables:
MyClass<T> m1 = ...
MyClass m2 = ...
Now call getOtherStuff()
:
List<String> list1 = m1.getOtherStuff();
List<String> list2 = m2.getOtherStuff();
The second has its generic type argument stripped off by the compiler because it is a raw type (meaning the parameterized type isn't supplied) even though it has nothing to do with the parameterized type.
I'll also mention my favourite declaration from the JDK:
public class Enum<T extends Enum<T>>
Apart from wildcarding (which is a mixed bag) I just think the .Net generics are better.
The introduction of generics into Java was a difficult task because the architects were trying to balance functionality, ease of use, and backward compatibility with legacy code. Quite expectedly, compromises had to be made.
There are some who also feel that Java's implementation of generics increased the complexity of the language to an unacceptable level (see Ken Arnold's "Generics Considered Harmful"). Angelika Langer's Generics FAQs gives a pretty good idea as to how complicated things can become.
Java generics are checked for correctness at compile time and then all type information is removed (the process is called type erasure. Thus, generic List<Integer>
will be reduced to its raw type, non-generic List
, which can contain objects of arbitrary class.
This results in being able to insert arbitrary objects to the list at runtime, as well as it's now impossible to tell what types were used as generic parameters. The latter in turn results in
ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if(li.getClass() == lf.getClass()) // evaluates to true
System.out.println("Equal");
If you listen to Java Posse #279 - Interview with Joe Darcy and Alex Buckley, they talk about this issue. That also links to a Neal Gafter blog post titled Reified Generics for Java that says:
Many people are unsatisfied with the restrictions caused by the way generics are implemented in Java. Specifically, they are unhappy that generic type parameters are not reified: they are not available at runtime. Generics are implemented using erasure, in which generic type parameters are simply removed at runtime.
That blog post, references an older entry, Puzzling Through Erasure: answer section, that stressed the point about migration compatibility in the requirements.
The goal was to provide backwards compatibility of both source and object code, and also migration compatibility.
Another side effect of them being compile-time and not run time is that you can't call the constructor of the generic type. So you can't use them to implement a generic factory...
public class MyClass {
public T getStuff() {
return new T();
}
}
--jeffk++