Wildcards and Generic Methods in Java - Reading, Writing, and Arithmetic (Page 2 of 5 )
We've glossed over an important issue so far in our discussion of wildcard types: namely, how can we use them? What kinds of types does the compiler enforce for variables and arguments which referred to the type variables in the generic class? For example, if we have a List<?> list of any instantiation type, what are the rules about putting objects into it and getting them back out? What is their type?
We have to take the two cases separately. Drawing on the analogy of a container, we'll call getting a return value from a method on an object as a specific type reading the object as a type. Conversely, we'll call passing arguments of a specific type to methods of the object writing the object as a type. So, for example, a List<Date> can be read and written as the Date type and a Trap<Mouse> has methods that can be read and written as the Mouse type.
To be more precise, though, we should say that List<Date> can be read as the Date type but can be written as any subtype of Date. After all, we could add a MyDate to a List<Date>. Let's look now at the wildcard instantiation List< ? extends Date >. We know it holds an instantiation of the List type on some type of Date. What more can we say about the elements of such a List, which could hold any instantiation of the Date type? Well, the elements will always be subtypes of Date. This means that, at a minimum, we should be able to read the object through our wildcard type as type Date:
List< ? extends Date > someDateList = new ArrayList<MyDate>(); ... Date date = someDateList.get( 0 ); // read as Date
The compiler lets us assign the value directly to a Date because it knows that whatever the instantiation of the List, the elements must be a subtype of Date. (Of course, we could have read the object as type Object or any supertype of Date if we'd wanted as well.)
But what about going the other way and writing? If someDatelist could be an instantiation of List on any subclass of Date, how can we know what type of objects to write to it? (How can we safely call its add() method?) The answer is that we can't. Since we don't know the correct type, the compiler won't let us write anything to the List through our wildcard instantiation of the type:
List< ? extends Date > someDateList = new ArrayList<MyDate>(); someDatelist.add( new Date() ); // Compile-time Error! someDatelist.add( new MyDate() ); // Compile-time Error!
Another way to put this is that since our wildcard instantiation has an upper bound of Date, we can read the type as Date. We'll reiterate that in the form of a rule in a moment.
Recall that an unbounded wildcard is really just a wildcard with a bound of type Object <? extends Object>. Obviously, even an unbounded wildcard instantiation holds objects that can be assigned to Object, so it's okay to read an unbounded wildcard as the Object type:
List<?> someList = new ArrayList<String>(); ... Object object = someList.get( 0 ); // read as Object
But, of course, we cannot know the actual type of the elements, so we cannot write to the list through our unbounded wildcard type.
What about lower bounds? Well, the situation is neatly reversed with respect to reading and writing. Since we know that the elements of any instantiation matching our lower bounded wildcard must be a supertype of the lower bound, we can write to the object as the lower bound type through our wildcard:
List< ? super MyDate > listAssignableMyDate = new ArrayList<Date>(); listAssignableMyDate.add( new MyDate() ); listAssignableMyDate.add( new Date() ); // Compile-time Error!
But not knowing what supertype of MyDate the elements are, we cannot read the list as any specific type. Of course, the List must still hold some type of Object,so we can always read the lower bounded list as type Object through the wildcard. The type Object is the default upper bound:
Object obj = listAssignableMyDate.get( 0 ); // read as Object
Whew. Well, having gone through that explanation, we can now sum it up concisely in an easy-to-remember rule:
Wildcard instantiations of generic types can be read as their upper bound and written as their lower bound.
To elaborate: all wildcard instantiations have an upper bound of Object even if none other is specified, so all wildcard instantiations can at least be read as type Object. But not all wildcards have a lower bound. Only those using the super construct have a lower bound and so only those wildcard instantiations can be written as a type more specific than Object.
<?>, <Object>, and the Raw Type
We've covered a lot of ground and the semantics can be a bit hard to follow. Let's exercise our knowledge by reviewing a few cases that may or may not have similarities.
Natural questions to ask are, What good is the unbounded wildcard anyway? Why not just use the raw type? How do unbounded wildcard instantiation and raw types compare? The first difference is that the compiler will issue unchecked warnings when we use methods of the raw type. But that's superficial. Why is the compiler warning us? It's because it cannot stop us from abusing our raw type by foisting the wrong type of objects on it. Using an unbounded wildcard is like putting on boxing gloves and saying that we want to play by the rules. Doing so comes at a cost. The compiler guarantees that we are safe by allowing us only the operations that it knows are safe, namely, reading as type Object (the upper bound of everything). The compiler does not let us write to an unbounded wildcard at all. So why use the unbounded wildcard? To play by the rules of generics and guarantee that we don't do anything unsafe.
Next, we can knock down any notion that an unbounded wildcard instantiation is similar to an instantiation on the type Object. Remember that a List<?> holds some instantiation of List. It could be a List<Date> for all we know. But a List<Object> is actually a list that holds concrete Object types. The List<Object> can be read and written as Object. The List<?> can only be read (not written) and only read as Object in a degenerate sense. The elements of List<?> are actually all of some unknown type. The elements of the unknown type list all have a common supertype that could be Object but could be some other common type more restrictive than Object. The knowledge of what "could be" in the List<?> doesn't do much for us in practice but means something completely different from List<Object>.
Finally, let's round out the comparisons by asking how List<Object> and the raw type compare. Now we're onto something. In fact, the raw type after erasure is effectively List<Object> as you'll recall. But in this case, were telling the compiler that that is okay. Here, we are asking for a type with elements that can hold any type safely and the compiler obliges. The answer to the question of how List<Object> and the raw type List compare is that List<Object> is the "generic safe" version of the raw type of yesterday.