Section 8.8. Wildcards


8.8. Wildcards

We mentioned earlier that the kinds of generic type instantiations discussed so far in this chapter have all been concrete type instantiations. We described this as meaning that all of the parameter arguments are real Java types. For example, List<String> and List<Date> are instantiations of the generic List class with the concrete types String and Date. Now we're going to look at another kind of generic type instantiation: wildcard instantiation.

As we'll see in this section, wildcards are Java's way of introducing polymorphism into the type parameter portion of the generic equation. A wildcard instantiation uses a question mark (?) in place of an actual type parameter at instantiation time and denotes that the type can be assigned any of a range of possible instantiations of the generic type. The ? wildcard by itself is called the unbounded wildcard and denotes that any type instantiation is acceptable (assignable to the type).

     List<?> anyInstantiationOfList = new ArrayList<Date>( );     anyInstantiationOfList = new ArrayList<String>( ); // another instantiation

In this snippet, we declared a variable anyInstantiationOfList whose type is the unbounded wildcard instantiation of the generic List type. (What a mouthful.) This means that the type we instantiated can be assigned any particular concrete instantiation of the List type, whether Dates, Strings, or Foos. Here, we assigned it a List<Date> first and, subsequently, a List<String>.

8.8.1. A Supertype of All Instantiations

The unbounded wildcard instantiation is a kind of supertype of all of these concrete instantiations. In contrast to the generic type relationships that we saw earlier, which followed only raw, "base" generic types, wildcards let us implement polymorphism on the parameter types. The unbounded wildcard is to generic type parameters what the Object type is to regular Java types: a supertype of everything.

     // A List<Object> is not a List<Date>!     List<Object> objectList = new ArrayList<Date>( ) // Error!     // A List<?> can be a List<Date>     List<?> anyList = new ArrayList<Date>( ); // Yes!

We are reminded in this example that List<Object> is not a List<Date>; polymorphism doesn't flow that way with generic instantiations of concrete types. But List<?>, the unbounded wildcard instantiation, can be assigned any instantiation of List. As we go on, we'll see that wildcards add a new dimension to the assignability of generic types.

8.8.2. Bounded Wildcards

A bounded wildcard is a wildcard that uses the extends keyword just as a type variable would, to limit the range of assignable types. For example:

     List<? extends Date> dateInstantiations = new ArrayList<Date>( );     dateInstantiations = new ArrayList<MyDate>( ); // another instantiation

Our dateInstantiations variable is limited to holding instantiations of List on parameter types of Date and its subclasses. So, we can assign it a List<Date> or a List<MyDate>. In the same way that the unbounded wildcard serves as a superclass for all instantiations of a generic type, bounded wildcards create more limited supertypes covering a narrower range of instantiations. In this case, our wildcard instantiation, List<? extends Date>, is the supertype of all instantiations of List on Date types. As with type parameter bounds, the bound Date is called the upper bound of the type.

Wildcard bounds may extend interfaces as well as use the & syntax to add interface requirements to the bound:

     Trap< ? extends Catchable & Releaseable > trap;

In this case, the instantiation serves as a supertype of the set of instantiations on types implementing both the Catchable and Releaseable interfaces.

8.8.3. Thinking Outside the Container

Let's be clear about what the wildcard means in the context of a container type such as List. The unbounded wildcard instantiation may be assigned any type instantiation, but it does ultimately refer to some particular type instantiation. A wildcard instantiation serves as the type of a variable, and that variable eventually holds some actual concrete instantiation of the generic type:

     List<?> someInstantiationOfList;     someInstantiationOfList = new ArrayList<Date>( );     someInstantiationOfList = new ArrayList<String>( );

In this example, our List<?> variable is either a List<String> or a List<Date>. It is not some new kind of List that can hold either String or Date elements.

In the same way, a wildcard with bounds ultimately holds one of the concrete instantiations assignable to its bounds. Imagine for a moment that we have a private class Foo with only one subclass Bar and no others. The expression Collection<? extends Foo> in this case means the set of two possibilities: either Collection<Foo> or Collection<Bar>. That is, either a Collection of elements with a common supertype of Foo or a collection of elements with a common supertype of Bar. Again, the wildcard instantiation matches either of those generic type instantiations. It does not create a new type of collection that can contain either Foos or Bars. (That is actually the job of Collection<Foo>, which can contain both Foo and Bar elements.)

For this reason, wildcard type instantiations are valid types for referencing an object, but they cannot be used as the type to create an instance of an object. In general, you cannot use a wildcard type with the new keyword to allocate an object instance because the wildcard denotes one or a possible set of objects. It doesn't make sense.

8.8.4. Lower Bounds

We saw the extends construct used to specify an upper bound for both type variables and wildcard instantiations. It implies a type that is "at the top" of the object hierarchy for the bound. Wildcard instantiations actually allow another type of bound called a lower bound as well. A lower bound is specified with the keyword super and, as you might guess, requires that instantiations be of a certain type or any of its supertypes, up to Object. For example:

     List< ? super MyDate > listOfAssignableFromMyDate;     listOfAssignableFromMyDate = new ArrayList<MyDate>( );     listOfAssignableFromMyDate = new ArrayList<Date>( );     listOfAssignableFromMyDate = new ArrayList<Object>( );

This wildcard instantiation creates a type that can hold any instantiation of List on the type MyDate or any of its supertypes. In our example world, that means the wildcard type can be assigned one of only three types: List<MyDate>, List<Date>, or List<Object>. Here, we have cut off the object inheritance hierarchy after three generations. No further subclasses of MyDate can be used.

As we hinted in the example, it may help to read ? super MyDate as "Assignable from MyDate." Lower bounds are useful for cases where we want to be sure that a particular container instantiation can hold a particular element type, without limiting it to just the specific type of the element. We'll show a good example of this when we talk about generic methods later. For now, just try to digest this as complementary to upper bounds.

One last thing about lower bounds: only the wildcard instantiation syntax can use the super keyword to refer to lower bounds. Bounds of type variables in generic class declarations cannot have lower bounds. Erasure replaces all references to the type variables with their upper bounds, so runtime types have no way to enforce the contract.

8.8.5. Reading, Writing, and Arithmetic

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.

8.8.6. <?>, <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, we're 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.

8.8.7. Wildcard Type Relationships

Before we leave our wild discussion of wildcard types, let's return one more time to the notion of wildcard type instantiations as types in the Java type system. Earlier in this chapter, we described how regular concrete instantiations of generic types are related by virtue of their "base" generic type inheritance, only with the proviso that their type parameters are exactly the same. Later, we tried to instill the idea that wildcard instantiations add an inheritance relationship to the type parameters, the other half of the generic instantiation. Now, we'll bring the two together. Things can get arcane pretty quickly, but the simple cases are easy to swallow.

The question is, If we have two different wildcard instantiations of a type or related types, how, if at all, are they related? For example, since an unbounded wildcard instantiation can hold any instantiation, can it be assigned a value with a more restrictive bound?

     List< ? extends Date > dateLists = ...;     List< ? >  anylists;     anyLists = dateLists; // Ok!

The answer is yes. For purposes of assignability, wildcard instantiations can be considered as types with possible supertype or subtype relationships determined by their bounds. Let's spell out the unbounded wildcard instantiation as it really is, an instantiation with an upper bound of Object:

     List< ? extends Date > dateLists = ...;     List< ? extends Object >  objectLists;     objectLists = dateLists; // Ok!

The rule is that if the "base" generic, raw type is assignable and the bounds of the wildcard instantiation are also assignable, the overall types are assignable. Let's look at another example:

     List< ? extends Integer > intLists = ...;     Collection< ? extends Number > numCollections;     numCollections = intLists; // Ok!

What this effectively says is that some List of Integer types can be treated as some Collection of Number types through the wildcard instantiation. If you think about it, you'll see that there is no conflict here. A List is certainly a Collection. And all we're doing is widening the type by which we can read the elements from Integer to Number. In neither case could we have written to the collection via the wildcard instantiation anyway.

What all this ultimately means is that with the introduction of wildcard instantiations, the type relationships of Java generic classes becomes two-dimensional. There is the raw type relationship to consider and then the wildcard parameter relationship. In fact, if you consider that generic classes may have more than one type parameter, the relationships can get even more complicated (N-dimensional). Fortunately, none of this comes up very often in the real world.



    Learning Java
    Learning Java
    ISBN: 0596008732
    EAN: 2147483647
    Year: 2005
    Pages: 262

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net