Permitting a Broader Argument Range
Employ three types of J2SE 1.5's wildcards as type arguments of parameterized types while recognizing their limitations
by Klaus Kreft and Angelika Langer
Posted May 19, 2004
Let's explore typical use of the different types of wildcards. We already saw a typical example for using a wildcard instantiation—see Part 1, "Wildcard Instantiations of Parameterized Types" (Java Pro Online, May 12, 2004)—in which we discussed the drawAllShapes() method. If we want the drawAllShapes() method to accept not only collections of shapes but also collections of subtypes of Shape, then we can achieve this result by declaring the argument as a Collection<? extends Shape>.
public void drawAllShapes(
Collection<? extends Shape>
c) {
for (Shape s : c) {
s.draw();
}
}
Collection<Triangle> c =
new LinkedList<Triangle>();
... fill the collection with
triangles ...
drawAllShapes(c);
Essentially, the wildcard is used to relax the requirements imposed on the method arguments and to permit a broader range of argument types.
Programmers familiar with parameterized methods might have noticed that the same effect can be achieved without wildcards as well: if we declare the drawAllShapes() method as a parameterized method, we would also allow collections of subtypes of Shape as arguments. The parameterization would look like this:
public <T extends Shape> void
drawAllShapes(
Collection<T> c) {
for (Shape s : c) {
s.draw();
}
}
The parameterized drawAllShapes() method accepts collections whose element type is a subtype of Shape, exactly as the nonparameterized version with the wildcard instantiation does. The difference is that the parameterized version does not restrict access to the method argument, while the wildcard instantiations disallow the invocation of methods that take arguments of the unknown type. In this example, it does not make any difference because the only method we invoke on the Shape type (or subtype thereof) is the draw () method, which does not take any arguments. In another context the parameterized method might be more flexible than its nonparameterized counterpart with the wildcard argument. The point to take home is: wildcards with an upper bound can often be replaced by parameterization with a bounded type parameter.
Using Unbounded Wildcards
Unbounded wildcards are used when the type of the type parameter does not matter, either because the object referred to is not used or because no methods of that type are invoked. A method that prints all elements in a collection serves as an example of using an unbounded wildcard:
void printAll(Collection<?> c) {
for (Object o : c)
System.out.println(
o.toString());
}
All that is required of the elements in the collection is that they must have a toString() method. Since the toString() method is defined in the Object class, this requirement is not significant. Basically, we need not know anything about the type of the objects contained in the collection. Hence, a declaration as Collection<?> is appropriate.
Note the difference between use of a Collection<?> and use of a Collection<Object>: a method declared as void printAll(Collection<Object> c) would not accept a LinkedList<String> as an argument, while a method declared as void printAll(Collection<?> c) permits it. Both expressions look like they denote a "collection of anything." The difference is that Collection<Object> is a concrete type, while Collection<?> is not a type. It just stands for a representative from the family of types that are instantiations of Collection. Therefore, a method parameter declared as Collection<Object> must be of exactly that type, or a subtype thereof. A method parameter declared as Collection<?> can be of any type that is an instantiation of Collection, or a subtype of any of these types.
The type Collection<?> is also different from the raw type Collection. Invocation of methods that would be flagged as errors when invoked through a reference of type Collection<?> would lead to "unchecked warning" when invoked through a reference of type Collection. Also, the raw type is a concrete type, of which objects and arrays can be created, while Collection<?> must not appear in new expressions.
Unbounded wildcards are also used for partial instantiations and express that no requirements are imposed on a type. Here is an example: suppose we have a parameterized pair class with two fields of different unknown types:
class Pair<S,T> {
private S first;
private T second;
...
<A extends S> void setFirst(
Pair<A,?> p) {
first = p.first;
}
...
}
The parameterized class Pair<S,T> has two instance fields of type S and T, respectively. The declaration of the parameter of the setFirst() method as a Pair<A,?> indicates that the method has no interest in the second instance field of the pair. The type Pair<A,?> would denote a partial instantiation of the parameterized Pair type.
Using Wildcards with a Lower Bound
Wildcards with a lower bound are used for a similar purpose as wildcards with an upper bound; they are used as arguments types of methods and relax the requirements imposed on the argument type. Let's take a look at an example. Consider a class hierarchy of value types such as these:
class Person {
private String firstName;
private String lastName;
private Date dateOfBirth;
...
}
class Employee extends Person {
private Person Supervisor;
...
}
If the classes in such a hierarchy of value classes were supposed to implement a parameterized interface such as Comparable, we run into the problem that super- and subclass cannot implement different instantiations of the same parameterized interface; although, semantically it would make a lot of sense. What we would like to do, but are not permitted to do, is this:
class Person implements
Comparable<Person> {
private String firstName;
private String lastName;
private Date dateOfBirth;
...
public int compareTo(
Person other) {
...
}
}
class Employee extends Person
implements
Comparable<Employee> {
// error: already implements
// Comparable
private Person Supervisor;
...
public int compareTo(
Employee other) {
...
}
}
The compiler rejects the declaration of the subclass because it would implement different instantiations of the same interface, namely Comparable<Person> and Comparable<Employee>, which is not permitted. The restriction stems from the fact that parameterized types and methods are translated by type erasure. In the process of type erasure the two instantiations of the parameterized interface are mapped onto the raw type, and there would be no longer a difference between the interface that the super- and subclass implement.
The result is that the superclass determines which instantiation of a parameterized interface all its subclasses implement. This result, on the other hand, renders it impossible to pass the subclass to certain methods, unless the methods use wildcards with lower bounds. Here is an example of such a method, but we must first fix our class hierarchy so that all classes in the hierarchy of Person classes would implement Comparable<Person>:
class Person implements
Comparable<Person> {
private String firstName;
private String lastName;
private Date dateOfBirth;
...
public int compareTo(
Person other) {
...
}
}
class Employee extends Person {
private Person Supervisor;
...
public int compareTo(
Person other) {
...
}
}
Now, consider a method that asks for comparable objects:
<T extends Comparable<T>> void
someMethod(T arg1, T arg2) {
... arg1.compareTo(arg2) ...
}
This method can be invoked with reference variables of type Person as arguments, but an argument of type Employee would not be accepted because an Employee is comparable to Person but not to an Employee. In other words, the Employee would not be within bounds. This code snippet illustrates the situation:
Person p1 = new Employee(
"Peter", "Miller",
new Date(4,3,69));
Person p2 = new Employee(
"Jim", "Anderson",
new Date(10,8,84));
someMethod(p1, p2); // fine
Employee e1 = new Employee(
"Peter", "Miller",
new Date(4,3,69));
Employee e2 = new Employee(
"Jim", "Anderson",
new Date(10,8,84));
someMethod(e1, e2); // error:
// arguments are not within
// bounds
As this example demonstrates, it is essential that methods that take parameters through a parameterized interface type are declared to accept upper bound wildcard instantiations of the parameterized interface instead of concrete instantiations. Otherwise, applicability of the method would be severely—and unnecessarily—restricted.
Take Five
Let's relax the method signature. If we declare the method using an upper bound wildcard instantiation—instead of the concrete instantiation—the method accepts super- and subclass objects as arguments, that is, Person and Employee objects would be permitted:
<T extends
Comparable<? super T>> void
someMethod(T arg1, T arg2) {
... arg1.compareTo(arg2) ...
}
This example nicely illustrates the need for lower bound wildcards. They help solve a problem that would otherwise remain insurmountable. Classes in a class hierarchy can never implement different instantiation of the same parameterized interface; they must implement the same parameterized interface, namely, the one that the topmost superclass decided to implement. Methods taking arguments that must implement a concrete instantiation of the parameterized interface type would accept only superclass objects. With the use of lower bound wildcard instantiations these methods are substantially more useful.
Note also that lower bound wildcards, which are different from upper bound wildcards, cannot be replaced by a parameterization of the method itself because type variables can only have upper bounds, but no lower bounds. For wildcards both variants exist.
While the upper bound wildcard solves the problem discussed previously, there are still cases in which not even wildcards come to the rescue. If the interface defined a method that returned a value of the wildcard type, instead of taking it as an argument, then we would be limited by the rule that methods returning values of the unknown type cannot be invoked through a reference to a lower bound wildcard. Here is an example using a parameterized interface Copyable that has a value returning the copy() method and a hierarchy of classes that implement the interface:
interface Copyable<T> {
T copy();
}
class Person implements
Copyable<Person> {
...
public Person copy() { ... }
}
class Employee extends Person {
...
public Employee copy() { ... }
}
Note, that we've been taking advantage of covariant return types, another new language feature in Java 1.5. The subclass's copy() method has a return type that differs from the return type of the superclass's copy() method. The difference is that the subclass method returns a subtype, while the superclass method return a supertype, which is allowed in Java 1.5. Despite the different return types, the subclass's copy() method overwrites the superclass's copy() method.
As discussed earlier, a method such as this would be problematic:
<T extends Copyable<T>> T
produceCopy(T arg) {
return arg.copy();
}
This method does not permit Employees as arguments:
Person p1 = new Employee(
"Peter", "Miller",
new Date(4,3,69));
Person p2 = produceCopy(p1);
// fine
Employee e1 = new Employee(
"Peter", "Miller",
new Date(4,3,69));
Employee e2 = produceCopy(e1);
// error: arguments are not
// within bounds
Using a lower bound wildcard would in theory allow Employees as arguments, but would disallow invocation of the copy() method inside the produceCopy() method:
<T extends Copyable<? super T>>
T produceCopy(T arg) {
return arg.copy(); // error:
// type "? super T"
// incompatible with type T
}
The value returned from the copy() method would be of type "? super T", which means it can be of any supertype of T. Arbitrary supertypes of T are not necessarily assignment compatible to the declared return type T, and for this reason the compiler rightly complains.
Wildcards are part of the language extension known as Java Generics and added as a new language feature in Java 1.5. Wildcards can be used as the type argument of a parameterized type and represent a member from a family of types. Three different types of wildcards exist: the unbounded wildcard, wildcards with a lower bound, and wildcards with an upper bound. Wildcard instantiations of parameterized types are typically used as parameter and return types in method declarations and permit a broader range of arguments than would be possible without the use of wildcards.
About the Authors
Klaus Kreft is a senior consultant and software architect for Siemens Business Services who has delivered consultancy services to large-scale industry projects for almost 20 years and has used Java since 1995. Angelika Langer is a freelance trainer/consultant who was involved in compiler construction in the '90s and has been watching closely the development of C++, Java, and C#. Her teaching is backed by over 12 years of work as a developer in the software industry and over 8 years of training and consulting. Angelika is also author of an independent curriculum of introductory and advanced C++ and Java courses. The authors are speakers at international conferences, they authored Standard C++ IOStreams and Locales, and they wrote numerous articles about C++ and Java, including columns for C++ Report and JavaSpektrum. Visit for further information. Contact Klaus at , and contact Angelika at .
|