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
Date
s, String
s, or Foo
s. Here, we assigned it a List<Date>
first and, subsequently, a
List<String>
.
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.
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.
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 Foo
s or Bar
s. (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.
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.
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 that
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 to 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 because our wildcard instantiation
has an upper bound of Date
, we can
only 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 OK 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. Because 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 because we do not know 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
.
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
or some other
common type that is 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 this is OK. 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.
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, which is 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, can an unbounded wildcard be assigned a value with a more restrictive bound because it can hold any instantiation?
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 become 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.
Get Learning Java, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.