|
Helping ordinary people create extraordinary websites! |
Java Theory and Practice: Generics GotchasBy Brian Goetz2005-03-23
Generics are not covariant While you might find it helpful to think of collections as being an abstraction of arrays, they have some special properties that collections do not. Arrays in the Java language are covariant -- which means that if Integer extends Number (which it does), then not only is an Integer also a Number, but an Interger[] is also a Number[], and you are free to pass or assign an Interger[] where a Number[] is called for. (More formally, if Number is a supertype of Integer, then Number[] is a supertype of Interger[].) You might think the same is true of generic types as well -- that List<Number> is a supertype of List<Integer>, and that you can pass a List<Integer> where a List<Number> is expected. Unfortunately, it doesn't work that way. It turns out there's a good reason it doesn't work that way: It would break the type safety generics were supposed to provide. Imagine you could assign a List<Integer> to a List<Number>. Then the following code would allow you to put something that wasn't an Integer into a List<Integer>: List<Integer> li = new ArrayList<Integer>(); List<Number> ln = li; // illegal ln.add(new Float(3.1415)); Because ln is a List<Number>, adding a Float to it seems perfectly legal. But if ln were aliased with li, then it would break the type-safety promise implicit in the definition of li -- that it is a list of integers, which is why generic types cannot be covariant. More covariance troubles Another consequence of the fact that arrays are covariant but generics are not is that you cannot instantiate an array of a generic type (new List<String>[3] is illegal), unless the type argument is an unbounded wildcard (new List<?>[3] is legal). Let's see what would happen if you were allowed to declare arrays of generic types: List<String>[] lsa = new List<String>[10]; // illegal Object[] oa = lsa; // OK because List<String> is a subtype of Object List<Integer> li = new ArrayList<Integer>(); li.add(new Integer(3)); oa[0] = li; String s = lsa[0].get(0); The last line will throw a ClassCastException, because you've managed to cram a List<Integer> into what should have been a List<String>. Because array covariance would have allowed you to subvert the type safety of generics, instantiating arrays of generic types (except for types whose type arguments are unbounded wildcards, like List<?>) has been disallowed. Tutorial Pages: » Identify and avoid some of the pitfalls in learning to use generics » Generics are not covariant » Construction delays » Generifying existing classes » Implications of erasure » Summary » Resources First published by IBM developerWorks |
|