THE Java™ Programming Language, Fourth Edition

(Jeff_L) #1

return arr;
}


This method works. If you invoke toArray_v1 on an instance of SingleLinkQueue and
pass a String[], then as many elements from the queue as the array can hold will be copied into it, and
then the array will be returned. The problem with this definition, as you may have already surmised from the
discussion of wildcards, is that it is too restrictive. It will, in the current example, accept only a String[]
and not, for example, an Object[] even though it is perfectly valid to store String objects into an
Object[].


Can we use a wildcard to help us out in this situation as well? Unfortunately not. To use a wildcard we'd try
writing something like this:


public ?[] toArray(?[]) { ... } // INVALID: won't compile


But this is simply not legal syntax: A wildcard represents an unknown type in a parameterized type definition
and '?[]' is not a parameterized type. What you need to do is introduce another type variable, say T, and use
it to describe the type of the array. And the way you introduce new type variables in a method is to declare the
method as a generic method.


You declare a generic method by defining type variables between the method modifiers and the method return
type, much as you would declare type variables for a generic class or interface. Here is how you would
actually define toArray as a generic method:


public T[] toArray(T[] arr) {
Object[] tmp = arr;
int i = 0;
for (Cell c = head;
c != null && i < arr.length;
c = c.getNext())
tmp[i++] = c.getElement();
return arr;
}


Notice that the type variable T is only used in the method header to constrain the parameter type and the
return type to be the samewithin the method body we don't really care what type T is. But also note that in the
method body we actually work with a local variable of type Object[], which refers to the actual array that
is passed in. This is necessary because the array is of type T, but getElement returns E, and these are not
the same typeT may be Object while E is String. We could cast E to T for the cast must succeed if T and
E are compatible, but such a cast would be an unchecked cast and would produce a compiler warningsee
"Erasure at Runtime" on page 267so we bypass the problem by using the Object[] variable.


By now you may be asking yourself: "Shouldn't there be some restriction between the types T and E, since
they must be compatible?" Logically, there could be such a restriction: T must be E or a supertype of E.
Unfortunately, there is no way to express this restriction. Only wildcards can be given a lower type bound, so
you cannot write as a type variable. In any case, such a restriction is not strictly necessary.
Suppose you have a SingleLinkQueue that you know you have filled only with String
instanceswhy shouldn't you be able to pass toArray a String[]? Yet String is not a supertype of
Object. So in fact there is no compile-time type checking between T and E to see if they are compatible. We
instead rely on the runtime type check that always occurs when a reference is stored into an array.