| Introduction | Iterator | <?> vs <Dog> vs <? |
| Why? | Static Methods | Under the Hood |
| The Ugly | Static Factories | Tips |
| Using Generics | Static Inference | Learning More |
| Comparator/Comparable | Serialised Objects | Links |
All the Java Collection classes, e.g. HashMap, ArrayList, TreeList can contain only generic Objects. The problem with this is you must keep track manually what sorts of object are really in each Collection. The compiler won’t warn you if you add a Cat to a Collection of Dogs.
Without genericity, in Java, you must, at run time, cast objects on the way into a Collection to make sure they are the right type, and cast them back again on the way out before you can use their methods. It is totally up to you as application programmer to enforce your rules about what sort of objects you want in each Collection. You can’t simply declare that a Collection will contain only Dog objects, and have attempts put anything else in flagged at compile time.
Java 1.5 has generics implemented with high overhead, requiring implicit casts both on the way into the collection and on the way out. The compiler automatically casts all objects going into your collection to Dog, and will automatically cast them back to Dog on the way out. Inside the collection, they are treated as generic Objects.
In theory, neither of these two time consuming casts are necessary. However, removing them would create a vulnerability in the JVM that could be exploited maliciously. The saving grace of the design in that Sun did not need to change the JVM or class file structure to implement it.
You can try out the JDK 1.5+ compiler with generics.
Further, types passed to a static method define the types of the parameters in the call, something quite different from passing a class as a parameter, which could only have an effect once the call had been made. e.g.
The theoretical magical property of the new syntax is that users of new generified classes need not change their source code, or even recompile. This was done so that Java 1.5 source could run on old 1.4 JVMs. Generics jumped through hoops to maintain compatibility with the old JVMs and class files. However, in practice, for other reasons, it turns out JDK 1.5 code won’t run on old JvMs. If you do decide to use generics in your code that calls the new generic Collection classes for example, you only need to change your variable declarations and where you instantiate objects. All the other code can stay identical, though many casts then become unnecessary. You really only need to understand generics to any extent when you write your own parameterised classes, usually containers of some sort. There is nothing to do for code that uses those classes, other than fix the errors the type checking uncovers.// You MAY NOT write code like this: HashMap<String,Integer>[] hs = new HashMap<String,Integer>[1000];This is nuts since all the Collection classes fundamentally depend on internally typed arrays. You can’t write code that involves arrays and generics without generating (or suppressing) unchecked conversion warnings. The excuse Sun gives is this goofiness is necessary because of type erasure — that all generic type information is checked then discarded at compile time. The excuse they give for type erasure was they wanted to make code with generics usable by older Java object code that knew nothing of genericity. You can’t actually do this, so the benefit was never realised.
To understand why type erasure forced this monstrous kludge, read up on ArrayStoreException. Even without generics, Java cannot tell purely at compile time when inserting a given Object into an array is legal. Since the type erasure means there is no generic information around at run time, Java cannot enforce generic type safety when Objects are stored in arrays.
I think the real reason they did it was political. They did not want to force the licenced vendors to make major changes to their JVMs. To kludge around the problem, look at how Sun implemented ArrayList.
javac.exe -source 1.5 -target 1.5 *.javato turn on generics. Otherwise they will be treated as syntax errors.
To create an ArrayList that can only hold Dog Objects. If you attempt to put anything else in there, (except subclasses like Dalmatians), you will find out at compile time. This is a great improvement on the old days when you sometimes found out about your errors when you partly exercised the code at run time.
This is all very well for container classes that treat their contents as simple Objects and never execute any methods on them. But what if you had a smart container that wanted to run some of the Dog methods on its contents? Then you would have to say something like this where D stands for the class of the variable type of the container. You define your new Kennel container that can only hold Dogs or their subclasses.// this il not legal java.util.Vector<Dog> dogs = new java.util.Vector<Dalmatian>();
// naive and unsuccessful attempt to create a new object of generic type T T thing = new T();This fails because of type erasure. All trace of generics disappears at run time leaving just raw objects. The runtime code looks like:
// What that code compiles to at run time: Object thing = new Object();To get the effect of being able to create an object of an arbitrary class you need to to pass a Class Object parameter and use reflection with newInstance. Further, your construction classes need to have no-argument constructors. Generics are extremely subtle and difficult. One shortcut is to think of a Sun class that has similar generics to one you are writing and have a peek at the source code in src.zip to see how Sun pulled it off. e.g. If you wanted to use an enum as a generic qualifier, see how Enumset did it. To see how to allocate an array of a generic type T, see how ArrayList did it. See Angelika Langer’s Generics FAQ for details on how the kludges work.
// How the sort method is defined in Collections.sort // Quite a bit more complex that using it, eh? public static <T extends Comparable<? super T>> void sort( List<T> list )The sort static method is designed to work with a class T that implements Comparable on T or some superclass of T, e.g. Object. (Infuriatingly, generics use the term extends when they really mean implements). The method wants to sort a List of such T class objects.
You would expect to invoke it then with:
However, the compiler is smart enough most of the time to guess the type(s) from the context. In this case the compiler knows that listOfDogs is a List< Dog> therefore the mysterious class T must be Dog. The compiler gets clues from the types of the parameters and suprisingly from the type on the left hand side of the equal sign which it matches with the return type allowing you to get away with the short form:// Short form of sort invocation // relying on compiler static type inference Collections.sort( listOfDogs );
So when is Java smart enough to figure out the types on its own via inferring? It sometimes feels as if it depends on the phases of the moon. Inferring has even the cleverest programmers scratching their heads. Nobody can figure out for certain why sometimes the compiler can infer the generic types and sometimes it cannot. So what can you do?
If you have Eclipse, you can rapidly experiment, removing the explicit types one by one and putting them back if Eclipse complains. If you don’t have Eclipse, you can do the same thing more slowly with a compile cycle.
The problem with removing type tags is if you remove one too many tags, you are flying without a net. You have effectively turned off type checking. It is difficult to tell the difference between allowing inference to check types and turning off type checking altogether. This is a serious flaw in the design of Java generics.
Generic type tags are useful documentation to let others know what your code is up to. That is an argument for leaving them in.
If you burn the generic types into your code in 100 places, it will be hard to modify later if you change that type. Ideally you should specify the type of a variable in only one place — where it is declared. That is an argument for removing the type tags wherever you can.
// generates type mismatch error ArrayList<Thing> things = ois.readObject();You can try to fix it with a cast like this:
// generates cast warning ArrayList<Thing>things = (ArrayList<Thing>)ois.readObject();but that generates a warning message. The problem is type erasure. The generic type is not stored with the serialised object. Java could check that the Object read back was an ArrayList, but not that it was an ArrayList< Thing>. Since it can’t guarantee, it gives the warning. The problem is the lame type erasure way generics were implemented. Had the type information been included, Java could check and the cast would be valid. So what do you do? You can just live with the warning, suppress it with an annotation like this:
// suppress unchecked warning @SuppressWarnings( "unchecked" ) void restore() { // Java cannot check that the object is actually an ArrayList<Thing>. // It will just trust that it is. ArrayList<Thing> things = (ArrayList<Thing>) ois.readObject(); ... }or you can copy the fields one by one like this: If your ArrayList is already allocated and final, you can do it this way:
// copy with a temporary array, generates no warning final ArrayList<Thing> things = new ArrayList<Thing>( INITIAL_SIZE ); ... final ArrayList<Object> temp = (ArrayList<Object>)ois.readObject(); things.clear(); for ( Object item : temp ) { things.add( (Thing)item ); }It is much simpler to read and write serialised arrays than ArrayLists. They don’t have this problem since you are not relying on generics for your type information. For arrays, the Java type system embeds the actual type in the ObjectStream. The problem is you may not be able to switch your ObjectStream ArrayLists to simple arrays when your clients have many files in the old serialised ArrayList format. Arrays are also slightly more compact.
ArrayList<Dog> is a collection permitted to contain Dogs or any subclass of Dog. An ArrayList< Dog> that incidentally contained only Dalmatians is a quite different beast from an ArrayList<Dalmatian> which can’t contain anything but Dalmatians (or their subclasses).
Collection<Object> is a heterogenous Collection, while Collection<?> is a homogenous Collection of elements of the same unknown type, e.g. Dog and its subclasses.
Consider the wildcard form of a type specification:
The wildcard form restricts our ability to put raw Dog objects into the Collection, but on the other hand it lets us deal with Collection< Dalmatian>, where we could not otherwise.
You will need to read Sun’s tutorial many times. It is as subtle as a Buddhist sutra. It makes me wonder if generics were a big mistake, introducing something so very difficult to solve a simple problem. Angelika Langer’s Generics FAQ is somewhat more accessible. Just keep reading and experimenting and more and more of what you read will make sense little by little. Unfortunately, I find the understanding does not stick and you have to keep going back to the well to have it continue to make sense.
Here is a complex example. If you understand this, you will be well on your way to understanding the fine points:
public static <D extends Dog & Comparable<? super D>> D selectSuitableDog( Collection<? extends D> );Means:
This static method above is designed to be used with class D that has the following properties:
You must pass a Collection to the method where the Collection contains Dog objects (where the Dog objects may be subclasses of Dog), or a more limited Collection of some subclass of Dog objects, e.g. a Collection<Dalmatian> of pure Dalmatian objects. Note that a Collection< Dog> where all elements happen to be Dalmatian is quite a different animal from a Collection< Dalmatian>where all elements are coerced to be Dalmatians (or subsets of Dalmatian).
Compared with the gobbledegook to define the method, your call to the method is very simple:
![]() |
and suggestions to improve this page to Roedy Green : | ||
| Canadian Mind Products | |||
| mindprod.com IP:[65.110.21.43] | |||
| Your face IP:[38.103.63.17] | The information on this page is for non-military use only. | ||
| You are visitor number 32,521. | Military use includes use by defence contractors. | ||
| You can get a fresh copy of this page from: | or possibly from your local J: drive (Java virtual drive/Mindprod website mirror) | ||
| http://mindprod.com/jgloss/generics.html | J:\mindprod\jgloss\generics.html | ||