Wildcard Types

   


It was known for some time among researchers of type systems that a rigid system of generic types is quite unpleasant to use. The Java designers invented an ingenious (but nevertheless safe) "escape hatch": the wildcard type. For example, the wildcard type

 Pair<? extends Employee> 

denotes any generic Pair type whose type parameter is a subclass of Employee, such as Pair<Manager>, but not Pair<String>.

Let's say you want to write a method that prints out pairs of employees, like this:

 public static void printBuddies(Pair<Employee> p) {    Employee first = p.getFirst();    Employee second = p.getSecond();    System.out.println(first.getName() + " and " + second.getName() + " are buddies."; } 

As you saw in the preceding section, you cannot pass a Pair<Manager> to that method, which is rather limiting. But the solution is simple: use a wildcard type:

 public static void printBuddies(Pair<? extends Employee> p) 

The type Pair<Manager> is a subtype of Pair<? extends Employee> (see Figure 13-3).

Figure 13-3. Subtype relationships with wildcards


Can we use wildcards to corrupt a Pair<Manager> through a Pair<? extends Employee> reference?

 Pair<Manager> managerBuddies = new Pair<Manager>(ceo, cfo); Pair<? extends Employee> wildcardBuddies = managerBuddies; // OK wildcardBuddies.setFirst(lowlyEmployee); // compile-time error 

No corruption is possible. The call to setFirst is a type error. To see why, let us have a closer look at the type Pair<? extends Employee>. Its methods look like this:

 ? extends Employee getFirst() void setFirst(? extends Employee) 

This makes it impossible to call the setFirst method. The compiler only knows that it needs some subtype of Employee, but it doesn't know which type. It refuses to pass any specific type after all, ? might not match it.

We don't have this problem with getFirst: It is perfectly legal to assign the return value of getFirst to an Employee reference.

This is the key idea behind bounded wildcards. We now have a way of distinguishing between the safe accessor methods and the unsafe mutator methods.

Supertype Bounds for Wildcards

Wildcard bounds are similar to type variable bounds, but they have an added capability you can specify a supertype bound, like this:

 ? super Manager 

This wildcard is restricted to all supertypes of Manager. (It was a stroke of good luck that the existing super keyword describes the relationship so accurately.)

Why would you want to do this? A wildcard with a supertype bound gives you the opposite behavior of the wildcards described on page 721. You can supply parameters to methods, but you can't use the return values. For example, Pair<? super Manager> has methods

 void set(? super Manager) ? super Manager get() 

The compiler doesn't know the exact type of the set method but can call it with any Manager object (or a subtype such as Executive), but not with an Employee. However, if you call get, there is no guarantee about the type of the returned object. You can only assign it to an Object.

Here is a typical example. We have an array of managers and want to put the manager with the lowest and highest bonus into a Pair object. What kind of Pair? A Pair<Employee> should be fair game or, for that matter, a Pair<Object> (see Figure 13-4). The following method will accept any appropriate Pair:

 public static void minMaxBonus(Manager[] a, Pair<? super Manager> result) {    if (a == null || a.length == 0) return;    Manager min = a[0];    Manager max = a[0];    for (int i = 1; i < a.length; i++)    {       if (min.getBonus() > a[i].getBonus()) min = a[i];       if (max.getBonus() < a[i].getBonus()) max = a[i];    }    result.setFirst(min);    result.setSecond(max); } 

Figure 13-4. A wildcard with a supertype bound


Intuitively speaking, wildcards with supertype bounds let you write to a generic object, wildcards with subtype bounds let you read from a generic objects.

Here is another use for supertype bounds. The Comparable interface is itself a generic type. It is declared as follows:

 public interface Comparable<T> {     public int compareTo(T other); } 

Here, the type variable indicates the type of the other parameter. For example, the String class implements Comparable<String>, and its compareTo method is declared as

 public int compareTo(String other) 

This is nice the explicit parameter has the correct type. Before JDK 5.0, other was an Object, and a cast was necessary in the implementation of the method.

Because Comparable is a generic type, perhaps we should have done a better job with the min method of the ArrayAlg class? We could have declared it as

 public static <T extends Comparable<T>> T min(T[] a) . . . 

This looks more thorough than just using T extends Comparable, and it would work fine for many classes. For example, if you compute the minimum of a String array, then T is the type String, and String is a subtype of Comparable<String>. But we run into a problem when processing an array of GregorianCalendar objects. As it happens, GregorianCalendar is a subclass of Calendar, and Calendar implements Comparable<Calendar>. Thus, GregorianCalendar implements Comparable<Calendar> but not Comparable<GregorianCalendar>.

In a situation such as this one, supertypes come to the rescue:

 public static <T extends Comparable<? super T>> T min(T[] a) . . . 

Now the compareTo method has the form

 int compareTo(? super T) 

Maybe it is declared to take an object of type T, or for example, when T is GregorianCalendar a supertype of T. At any rate, it is safe to pass an object of type T to the compareTo method.

To the uninitiated, a declaration such as <T extends Comparable<? super T>> is bound to look intimidating. This is unfortunate, because the intent of this declaration is to help application programmers by removing unnecessary restrictions on the call parameters. Application programmers with no interest in generics will probably learn quickly to gloss over these declarations and just take for granted that library programmers will do the right thing. If you are a library programmer, you'll need to get used to wildcards, or your users will curse you and throw random casts at their code until it compiles.

Unbounded Wildcards

You can even use wildcards with no bounds at all, for example, Pair<?>. At first glance, this looks identical to the raw Pair type. Actually, the types are very different. The type Pair<?> has methods such as

 ? getFirst() void setFirst(?) 

The return value of getFirst can only be assigned to an Object. The setFirst method can never be called, not even with an Object. That's the essential difference between Pair<?> and Pair: you can call the setObject method of the raw Pair class with any Object.

Why would you ever want such a wimpy type? It is useful for very simple operations. For example, the following method tests whether a pair contains a given object. It never needs the actual type.

 public static boolean hasNulls(Pair<?> p) {    return p.getFirst() == null || p.getSecond() == null; } 

You could have avoided the wildcard type by turning contains into a generic method:

 public static <T> boolean hasNulls(Pair<T> p) 

However, the version with the wildcard type seems easier to read.

Wildcard Capture

Let us write a method that swaps the elements of a pair:

 public static void swap(Pair<?> p) 

A wildcard is not a type variable, so we can't write code that uses ? as a type. In other words, the following would be illegal:

 ? t = p.getFirst(); // ERROR p.setFirst(p.getSecond()); p.setSecond(t); 

That's a problem because we need to temporarily hold the first element when we do the swapping. Fortunately, there is an interesting solution to this problem. We can write a helper method, swapHelper, like this:

 public static <T> void swapHelper(Pair<T> p) {    T t = p.getFirst();    p.setFirst(p.getSecond());    p.setSecond(t); } 

Note that swapHelper is a generic method, whereas swap is not it has a fixed parameter of type Pair<?>.

Now we can call swapHelper from swap:

 public static void swap(Pair<?> p) { swapHelper(p); } 

In this case, the parameter T of the swapHelper method captures the wildcard. It isn't known what type the wildcard denotes, but it is a definite type, and the definition of <T>swapHelper makes perfect sense when T denotes that type.

Of course, in this case, we were not compelled to use a wildcard there is nothing wrong with using a type parameter, as in the swapHelper method. However, consider this example in which a wildcard type occurs naturally in the middle of a computation:

 public static void maxMinBonus(Manager[] a, Pair<? super Manager> result) {    minMaxBonus(a, result);    PairAlg.swapHelper(result); // OK--swapHelper captures wildcard type } 

Here, the wildcard capture mechanism cannot be avoided.

Wildcard capture is only legal in very limited circumstances. The compiler must be able to guarantee that the wildcard represents a single, definite type. For example, the T in ArrayList<Pair<T>> can never capture the wildcard in ArrayList<Pair<?>>. The array list might hold two Pair<?>, each of which has a different type for ?.

The test program in Example 13-3 gathers up the various methods that we discussed in the preceding sections, so that you can see them in context.

Example 13-3. PairTest3.java
   1. import java.util.*;   2.   3. public class PairTest3   4. {   5.   public static void main(String[] args)   6.   {   7.      Manager ceo = new Manager("Gus Greedy", 800000, 2003, 12, 15);   8.      Manager cfo = new Manager("Sid Sneaky", 600000, 2003, 12, 15);   9.      Pair<Manager> buddies = new Pair<Manager>(ceo, cfo);  10.      printBuddies(buddies);  11.  12.      ceo.setBonus(1000000);  13.      cfo.setBonus(500000);  14.      Manager[] managers = { ceo, cfo };  15.  16.      Pair<Employee> result = new Pair<Employee>();  17.      minMaxBonus(managers, result);  18.      System.out.println("first: " + result.getFirst().getName()  19.        + ", second: " + result.getSecond().getName());  20.      maxMinBonus(managers, result);  21.      System.out.println("first: " + result.getFirst().getName()  22.         + ", second: " + result.getSecond().getName());  23.   }  24.  25.   public static void printBuddies(Pair<? extends Employee> p)  26.   {  27.      Employee first = p.getFirst();  28.      Employee second = p.getSecond();  29.      System.out.println(first.getName() + " and " + second.getName() + " are buddies.");  30.   }  31.  32.   public static void minMaxBonus(Manager[] a, Pair<? super Manager> result)  33.   {  34.      if (a == null || a.length == 0) return;  35.      Manager min = a[0];  36.      Manager max = a[0];  37.      for (int i = 1; i < a.length; i++)  38.      {  39.         if (min.getBonus() > a[i].getBonus()) min = a[i];  40.         if (max.getBonus() < a[i].getBonus()) max = a[i];  41.      }  42.      result.setFirst(min);  43.      result.setSecond(max);  44.   }  45.  46.   public static void maxMinBonus(Manager[] a, Pair<? super Manager> result)  47.   {  48.      minMaxBonus(a, result);  49.      PairAlg.swapHelper(result); // OK--swapHelper captures wildcard type  50.   }  51. }  52.  53. class PairAlg  54. {  55.    public static boolean hasNulls(Pair<?> p)  56.    {  57.       return p.getFirst() == null || p.getSecond() == null;  58.    }  59.  60.    public static void swap(Pair<?> p) { swapHelper(p); }  61.  62.    public static <T> void swapHelper(Pair<T> p)  63.    {  64.       T t = p.getFirst();  65.       p.setFirst(p.getSecond());  66.       p.setSecond(t);  67.    }  68. }  69.  70. class Employee  71. {  72.    public Employee(String n, double s, int year, int month, int day)  73.    {  74.       name = n;  75.       salary = s;  76.       GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);  77.       hireDay = calendar.getTime();  78.    }  79.  80.    public String getName()  81.    {  82.       return name;  83.    }  84.  85.    public double getSalary()  86.    {  87.       return salary;  88.    }  89.  90.    public Date getHireDay()  91.    {  92.       return hireDay;  93.    }  94.  95.    public void raiseSalary(double byPercent)  96.    {  97.       double raise = salary * byPercent / 100;  98.       salary += raise;  99.    } 100. 101.    private String name; 102.    private double salary; 103.    private Date hireDay; 104. } 105. 106. class Manager extends Employee 107. { 108.    /** 109.      @param n the employee's name 110.      @param s the salary 111.      @param year the hire year 112.      @param month the hire month 113.      @param day the hire day 114.   */ 115.   public Manager(String n, double s, int year, int month, int day) 116.   { 117.      super(n, s, year, month, day); 118.      bonus = 0; 119.   } 120. 121.   public double getSalary() 122.   { 123.      double baseSalary = super.getSalary(); 124.      return baseSalary + bonus; 125.   } 126. 127.   public void setBonus(double b) 128.   { 129.      bonus = b; 130.   } 131. 132.   public double getBonus() 133.   { 134.      return bonus; 135.   } 136. 137.   private double bonus; 138. } 


       
    top



    Core Java 2 Volume I - Fundamentals
    Core Java(TM) 2, Volume I--Fundamentals (7th Edition) (Core Series) (Core Series)
    ISBN: 0131482025
    EAN: 2147483647
    Year: 2003
    Pages: 132

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