Wildcarding a Generic Parameter

Say we want to write a method that will shuffle the elements in a Collection. We can shuffle a list regardless of what type the elements are. We don't want to have to write one shuffle method that works on List<Integer> and another for List<String> and another for List<Byte>. We need to give shuffle() a parameter that is a “List of anything” and have it move elements around. The usual way to express “List of anything” is “List of Object”, which in the new world of generics is written “List <Object>”

However, there's a big problem with passing around a nice, type-safe, homogeneous “List <String>” and letting someone untrustworthy, like our shuffle() method, get their hands on it as a “List <Object>” A parameter that points to a “List <Object>” can easily be used to add any Objects to the list: Strings, Integers, Timestamps, or anything else. The whole generics initiative is about limiting collections to only hold one type of object, and endowing the compiler with the information to check this. Unhappily List<Object> would blow our type-safe homogeneous collection model out of the water.

There's a piece of new syntax introduced here to express “parameterized type, but I don't care what it was parameterized with, they all match”. It's called a wildcard, and it's written like this:

LinkedList <?>

You pronounce it “LinkedList of unknown”, and it's intended to make you think of regular expression symbols where a “?” matches any single character. LinkedList<?> is the superclass of every LinkedList<T>. Collection<?> is “any class that implements Collection”. Wildcards can be used with any parameterized type. This is the actual declaration of the shuffle() method in class java.util.Collections

static void shuffle(List<?> mylist, Random rnd)

Shuffle will thus take an argument that is a List of any type, just as we need.

With <?> come a couple of rules to preserve type safety:

  • The compiler will treat parameterized types as incompatible with each other. I.e. List<A> and List<B> are incompatible and can't be assigned to each other, even when the types A and B are parent and child. The compiler must prevent an assignment like this:

    LinkedList <Number> parent;
    LinkedList <Integer> child;   // Integer is a subclass of Number
    parent = child;  // compiler must reject this as "inconvertible"

  • The elements of an array can be an unbounded wildcard type, such as List<?>,

    List<?>[] arrayOfLists;  // OK

but no other kind of generic type is acceptable in an array.

List<Integer>[] arrayOfLists; // NOT ALLOWED!!

To expand on that last point, a declaration “List<?>[] arrayOfLists;“ says “arrayOfLists is an array; each element of the array holds a list of unknown type”. You can therefore do list operations on an array element, but if you pull an individual element out, you can only do object operations on it. Furthermore, the list is incompatible with any other kind of list you might have, even a parent or child of the actual type.

Here is how you would declare and initialize an array containing an unbounded wildcard type:

List<?>[] arrayOfLists = new List<?>[128];

Here is something (a List of anything) that you could put in the array:

LinkedList<Double> ld = new LinkedList<Double>();
ld.add( 2.71 );

Here is how you could put it into the arrayOfLists:

arrayOfLists[0] = ld;

You could use the array as an actual parameter:

example(arrayOfLists);

Here is the corresponding method body that uses the array as a formal parameter:

static public void example(List<?>[] arrayOfLists) {
     List<?> list = arrayOfLists[0];
     int size = list.size();
}

Inside the method you can invoke methods of List, such as size(), on elements of the array parameter.

Bounds on wildcards

There are two refinements of a wildcard on a generic parameter: forcing a wildcard generic parameter to be a subtype of something, and forcing it to be a supertype of something. Remember bounds—the feature that allowed you to force a generic type parameter to extend some other class? Here's a reminder:

class Example <T extends java.lang.Number>

That says “When you instantiate this class, the actual type parameter must be Number or a subclass of Number, and therefore I can call methods of Number on objects of type T inside class Example”.

It's possible to add bounds to a wildcard, to require that the parameter of the parameterized type implement some interface or extend some class. That way you can rely on those methods being available, and you can call them from inside your method.

The abstract class Number is the superclass of classes BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, and Short. Say you wanted to process linked lists of Numbers, and keep it all type-safe. Here is the syntax:

LinkedList<? extends Number>

You could use it as the argument to a method that prints the first element of any list of numbers:

static void printFirst(LinkedList<? extends Number> n) {
    Number e = n.getFirst();
    System.out.println(e);
}

You could set up some lists of Numbers and call your printFirst() method as follows:

LinkedList<Double> ld = new LinkedList<Double>();
ld.add( 2.71 );
printFirst( ld );

LinkedList<Integer> li = new LinkedList<Integer>();
li.add( 21 );
printFirst( li );

The key point here is that we have a way to express a formal parameter to a method such that it can accept many different varieties of a generic type as the actual parameter. The terminology is that “Number is the upper bound” of the wildcard. Use an upper bound on a wildcard when you want to process elements from several collections of related types, in a type-safe way. Inside printFirst you can invoke any of the methods of LinkedList on the argument, n. You can invoke any of the methods of Number on elements that you get from the list. You are not allowed to use the wildcarded variable, n, to write into the linked list.

Lower bounds on wildcards

You can also specify a lower bound on a generic that's a parameter to one of your methods:

LinkedList<? super Integer>

That denotes a type that is required to be the class Integer or a superclass (parent class). The superclasses of Integer are Number and then Object. It's the opposite of requiring a type to be a child class, and so this is called a lower bound.

The most common use of a lower bound is in conjunction with assignments. An instance of a superclass can always hold an instance of one of its child classes. If a programmer tells the compiler this thing has to be Integer or a supertype of Integer, pretty soon you'll probably see it get assigned an object of this other thing that has to be Integer or a subtype of Integer.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.45.80