Wildcard Capture – Generics

11.9 Wildcard Capture

As we have seen, a wildcard can represent a family of types. However, the compiler needs to have a more concrete notion of a type than a wildcard in order to do the necessary type checking. Internally, the compiler represents the wildcard by some anonymous but specific type. Although this type is unknown, it belongs to the family of types represented by the wildcard. This specific but unknown type is called the capture of the wildcard.

Compiler messages about erroneous usage of wildcards often refer to the capture of a wildcard. Here are some examples of such error messages, based on compiling the following code:

Click here to view code image

// File: WildcardCapture.java

Node<?>                 anyNode;
Node<? super Number>    supNumNode;
Node<Integer> intNode = anyNode;                // (1) Compile-time error!
Node<? extends Number> extNumNode = supNumNode; // (2) Compile-time error!
anyNode.setData(“Trash”);                       // (3) Compile-time error!

The assignment at (1) results in the following rather cryptic error message:

Click here to view code image

WildcardCapture.java:10: error: incompatible types: Node<CAP#1> cannot be
converted to Node<Integer>
    Node<Integer> intNode = anyNode;                // (1) Compile-time error!
                            ^
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?

The type of the reference anyNode is Node<CAP#1>. The name CAP#1 is used by the compiler to designate the type capture of the wildcard (“capture of ?”) at (1). The type of the reference intNode is Node<Integer>. The reference value of a Node<CAP#1> cannot be assigned to a Node<Integer> reference. Whatever the type capture of the wildcard is, it cannot be guaranteed to be Integer, and the assignment is rejected. To put it another way, the assignment involves a narrowing reference conversion, requiring an explicit cast which is not provided: Node<?> is the supertype of all invocations of the generic class Node<E>.

The error message below for the assignment at (2) shows the type capture CAP#1 of the lower bounded wildcard at (2) to be “capture of ? super Number”. Figure 11.5, p. 583, also shows that the Node<capture of ? super Number> and Node<? extends Number> types are unrelated.

Click here to view code image

WildcardCapture.java:11: error: incompatible types: Node<CAP#1> cannot be
converted to Node<? extends Number>
    Node<? extends Number> extNumNode = supNumNode; // (2) Compile-time error!
                                        ^
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object super: Number from capture of ? super Number

The method call at (3) results in the following error message:

Click here to view code image

WildcardCapture.java:12: error: incompatible types: String cannot be converted to
CAP#1
    anyNode.setData(“Trash”);                       // (3) Compile-time error!
                    ^
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?

The type of the reference anyNode is Node<?> and the type of the formal parameter in the method declaration is CAP#1, where CAP#1 is “capture of ?”. The type of the actual parameter in the method call is String, which is not compatible with CAP#1. The call is not allowed. As we have seen earlier, with a <?> reference we cannot put anything into a data structure, except nulls.

If we have the following method in the class MyStack:

Click here to view code image

public static <T> void move(MyStack<? extends T> srcStack,
                            MyStack<? super T> dstStack) {
  while (!srcStack.isEmpty())
    dstStack.push(srcStack.pop());
}

and we try to compile the following client code in the source file MyStackUser.java:

Click here to view code image

MyStack<?> anyStack;
MyStack.move(anyStack, anyStack);   // Compile-time error!

the compiler issues the following error message:

Click here to view code image

MyStackUser.java:68: error: method move in class MyStack<E#2> cannot be applied to
given types;
      MyStack.move(anyStack, anyStack);     // Compile-time error!
             ^
  required: MyStack<? extends E#1>,MyStack<? super E#1>
  found: MyStack<CAP#1>,MyStack<CAP#2>
  reason: cannot infer type-variable(s) E#1
    (argument mismatch; MyStack<CAP#2> cannot be converted to MyStack<? super E#1>)
  where E#1,E#2 are type-variables:
    E#1 extends Object declared in method <E#1>move(MyStack<? extends E#1>,My-
Stack<? super E#1>)
    E#2 extends Object declared in class MyStack
  where CAP#1,CAP#2 are fresh type-variables:
    CAP#1 extends Object from capture of ?
    CAP#2 extends Object from capture of ?

The error message shows that each occurrence of a wildcard in a statement is represented by a distinct type capture, namely CAP#1 and CAP#2. We see that the signature of the move() method is move(MyStack<? extends E#1>, MyStack<? super E#1>). The type of the reference anyStack is MyStack<?>. The static types of the two arguments in the method call are MyStack<CAP#1> and MyStack<CAP#2>, where CAP#1 and CAP#1 designate two distinct type captures (capture of ?). The signature of the argument list is (MyStack<CAP#1>, MyStack<CAP#2>). The type parameter T cannot be inferred from the types of the arguments, as the stacks are considered to be of two different types, and therefore, the call is rejected.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *