We know now that parameterized types share a common, raw
type. This is why our parameterized List<Date> is just a List at runtime. In fact, we can assign any
instantiation of List to the raw type
if we want:
Listlist=newArrayList<Date>();
We can even go the other way and assign a raw type to a specific instantiation of the generic type:
List<Date>dates=newArrayList();// unchecked warning
This statement generates an unchecked warning on the assignment, but
thereafter the compiler trusts that the list contained only Dates prior to the assignment. It is also
permissible, albeit pointless, to perform a cast in this statement. We’ll
talk about casting to generic types a bit later.
Whatever the runtime types, the compiler is running the show and does not let us assign things that are clearly incompatible:
List<Date>dates=newArrayList<String>();// Compile-time Error!
Of course, the ArrayList<String> does not implement the
methods of List<Date> conjured by
the compiler, so these types are incompatible.
But what about more interesting type relationships? The List interface, for example, is a subtype of the
more general Collection interface. Is a
particular instantiation of the generic List also assignable to some instantiation of
the generic Collection? Does it depend
on the type parameters and their relationships? Clearly, a List<Date> is not a Collection<String>. But is a List<Date> a Collection<Date>? Can a List<Date> be a Collection<Object>?
We’ll just blurt out the answer first, then walk through it and explain. The rule is that for the simple types of generic instantiations we’ve discussed so far, inheritance applies only to the “base” generic type and not to the parameter types. Furthermore, assignability applies only when the two generic types are instantiated on exactly the same parameter type. In other words, there is still one-dimensional inheritance, following the base generic class type, but with the additional restriction that the parameter types must be identical.
For example, recalling that a List is a type of Collection, we can assign instantiations of
List to instantiations of Collection when the type parameter is exactly
the same:
Collection<Date>cd;List<Date>ld=newArrayList<Date>();cd=ld;// Ok!
This code snippet says that a List<Date> is a Collection<Date>—pretty intuitive. But
trying the same logic on a variation in the parameter types fails:
List<Object>lo;List<Date>ld=newArrayList<Date>();lo=ld;// Compile-time Error! Incompatible types.
Although our intuition tells us that the Dates in that List could all live happily as Objects in a List, the assignment is an error. We’ll explain
precisely why in the next section, but for now just note that the type
parameters are not exactly the same and that there is no inheritance
relationship among parameter types in generics. This is a case where
thinking of the instantiation in terms of types and not in terms of what
they do helps. These are not really a “list of dates” and a “list of
objects,” but more like a DateList and
an ObjectList, the relationship of
which is not immediately obvious.
Try to pick out what’s OK and what’s not OK in the following example:
Collection<Number>cn;List<Integer>li=newArrayList<Integer>();cn=li;// Compile-time Error! Incompatible types.
It is possible for an instantiation of List to be an instantiation of Collection, but only if the parameter types are
exactly the same. Inheritance doesn’t follow the parameter types and this
example fails.
One more thing: earlier we mentioned that this rule applies to the simple types of instantiations we’ve discussed so far in this chapter. What other types are there? Well, the kinds of instantiations we’ve seen so far where we plug in an actual Java type as a parameter are called concrete type instantiations. Later we’ll talk about wildcard instantiations, which are akin to mathematical set operations on types. We’ll see that it’s possible to make more exotic instantiations of generics where the type relationships are actually two-dimensional, depending both on the base type and the parameterization. But don’t worry: this doesn’t come up very often and is not as scary as it sounds.
It’s a reasonable question. Even with our brains thinking
of arbitrary DateList and ObjectList types, we can still ask why they
couldn’t be assignable. Why shouldn’t we be able to assign our List<Date> to a List<Object> and work with the Date elements as Object types?
The reason gets back to the heart of the rationale for generics
that we discussed in the introduction: changing APIs. In the simplest
case, supposing an ObjectList type
extends a DateList type, the DateList would have all of the methods of
ObjectList and we could still insert
Objects into it. Now, you might
object that generics let us change the APIs, so that doesn’t apply
anymore. That’s true, but there is a bigger problem. If we could assign
our DateList to an ObjectList variable, we would have to be able
to use Object methods to insert
elements of types other than Date
into it. We could alias the DateList as an ObjectList and try to trick it into accepting
some other type:
DateListdateList=newDateList();ObjectListobjectList=dateList;// Can't really do thisobjectList.add(newFoo());// should be runtime error!
We’d expect to get a runtime error when the actual DateList implementation
was presented with the wrong type of object. And therein lies the
problem. Java generics have no runtime representation. Even if this
functionality were useful, there is no way with the current scheme for
Java to know what to do at runtime. Another way to look at it is that
this feature is simply dangerous because it allows for an error at
runtime that couldn’t be caught at compile time. In general, we’d like
to catch type errors at compile time. By disallowing these assignments,
Java can guarantee that your code is typesafe if it compiles with no
unchecked warnings.
Actually, that last sentence is not entirely true, but it doesn’t have to do with generics; it has to do with arrays. If this all sounds familiar to you, it’s because we mentioned it previously in relation to Java arrays. Array types have an inheritance relationship that allows this kind of aliasing to occur:
Date[]dates=newDate[10];Object[]objects=dates;objects[0]="not a date";// Runtime ArrayStoreException!
However, arrays have runtime representations as different classes
and they check themselves at runtime, throwing an ArrayStoreException in just this case. So in
theory, Java code is not guaranteed typesafe by the compiler if you use
arrays in this way.