Section 6.2. Instance Tests and Casts


6.2. Instance Tests and Casts

Instance tests and casts depend on examining types at run time, and hence depend on reification. For this reason, an instance test against a type that is not reifiable reports an error, and a cast to a type that is not reifiable usually issues a warning.

As an example, consider the use of instance tests and casts in writing equality. Here is a fragment of the definition of the class Integer in java.lang (slightly simplified from the actual source):

 public class Integer extends Number {   private final int value;   public Integer(int value) { this.value=value; }   public int intValue() { return value; }   public boolean equals(Object o) {     if (o instanceof Integer) {       return value == ((Integer)o).intValue();     } else return false;   }   ... } 

The equality method takes an argument of type Object, checks whether the object is an instance of class Integer, and, if so, casts it to Integer and compares the values of the two integers. This code works because Integer is a reifiable type: all of the information needed to check whether an object is an instance of Integer is available at run time.

Now consider how one might define equality on lists, as in the class AbstractList in java.util. A naturalbut incorrectway to define this is as follows:

 import java.util.*; public abstract class AbstractList<E>   extends AbstractCollection<E> implements List<E> {   public boolean equals(Object o) {     if (o instanceof List<E>) {  // compile-time error       Iterator<E> it1 = iterator();       Iterator<E> it2 = ((List<E>)o).iterator();  // unchecked cast       while (it1.hasNext() && it2.hasNext()) {         E e1 = it1.next();         E e2 = it2.next();         if (!(e1 == null ? e2 == null : e1.equals(e2)))           return false;       }       return !it1.hasNext() && !it2.hasNext();     } else return false;   }   ... } 

Again, the equality method takes an argument of type Object, checks whether the object is an instance of type List<E>, and, if so, casts it to List<E> and compares corresponding elements of the two lists. This code does not work because List<E> is not a reifiable type: some of the information needed to check whether an object is an instance of List<E> is not available at run time. You can test whether an object implements the interface List, but not whether its type parameter is E. Indeed, information on E is missing doubly, as it is not available for either the receiver or the argument of the equals method.

(Even if this code worked, there is a further problem. The contract for equality on lists doesn't mention types. A List<Integer> may be equal to a List<Object> if they contain the same values in the same order. For instance, [1,2,3] should be equal to itself, regardless of whether it is regarded as a list of integers or a list of objects.)

Compiling the preceding code reports two problems, an error for the instance test and an unchecked warning for the cast:

 % javac -Xlint:unchecked AbstractList.java AbstractList.java:6: illegal generic type     for instanceof     if (!(o instanceof List<E>)) return false;  // compile-time error                            ^ AbstractList.java:8: warning: [unchecked] unchecked cast found   : java.lang.Object required: List<E>     Iterator<E> it2 = ((List<E>)o).iterator();  // unchecked cast                                 ^ 1 error 1 warning 

The instance check reports an error because there is no possible way to test whether the given object belongs to the type List<E>. The cast reports an unchecked warning; it will perform the cast, but it cannot check that the list elements are, in fact, of type E.

To fix the problem, we replace the nonreifiable type List<E> with the reifiable type List<?>. Here is a corrected definition (again, slightly simplified from the actual source):

 import java.util.*; public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {   public boolean equals(Object o) {     if (o instanceof List<?>) {       Iterator<E> it1 = iterator();       Iterator<?> it2 = ((List<?>)o).iterator();       while (it1.hasNext() && it2.hasNext()) {         E e1 = it1.next();         Object e2 = it2.next();         if (!(e1 == null ? e2 == null : e1.equals(e2)))           return false;       }       return !it1.hasNext() && !it2.hasNext();     } else return false;   }   ... } 

In addition to changing the type of the instance test and the cast, the type of the second iterator is changed from Iterator<E> to Iterator<?>, and the type of the second element is changed from E to Object. The code type-checks, because even though the element type of the second iterator is unknown, it is guaranteed that it must be a subtype of Object, and the nested call to equals requires only that its second argument be an object.

(This code properly satisfies the contract for equality on lists. Now a List<Integer> will be equal to a List<Object> if they contain the same values in the same order.)

Alternative fixes are possible. Instead of the wildcard types List<?> and Iterator<?>, you could use the raw types List and Iterator, which are also reifiable. We recommend using unbounded wildcard types in preference to raw types because they provide stronger static typing guarantees; many mistakes that are caught as an error when you use unbounded wildcards will only be flagged as a warning if you use raw types. Also, you could change the declaration of the first iterator to Iterator<?> and of the first element to Object, so that they match the second iterator, and the code will still type-check. We recommend always using type declarations that are as specific as possible; this helps the compiler to catch more errors and to compile more-efficient code.

Nonreifiable Casts An instance test against a type that is not reifiable is always an error. However, in some circumstances a cast to a type that is not reifiable is permitted.

For example, the following method converts a collection to a list:

 public static <T> List<T> asList(Collection<T> c)   throws InvalidArgumentException {   if (c instanceof List<?>) {     return (List<T>)c;   } else throw new InvalidArgumentException("Argument not a list"); } 

Compiling this code succeeds with no errors or warnings. The instance test is not in error because List<?> is a reifiable type. The cast does not report a warning because the source of the cast has type Collection<T>, and any object with this type that implements the interface List must, in fact, have type List<T>.

Unchecked casts Only rarely will the compiler be able to determine that if a cast to a nonreifiable type succeeds then it must yield a value of that type. In the remaining cases, a cast to a type that is not reifiable is flagged with an unchecked warning, whereas an instance test against a type that is not reifiable is always caught as an error. This is because there is never any point to an instance test that cannot be performed, but there may be a point to a cast that cannot be checked.

Type systems deduce facts about programsfor instance, that a certain variable always contains a list of strings. But no type system is perfect; there will always be some facts that a programmer can deduce but that the type system does not. To permit the programmer a workaround in such circumstances, the compiler issues warnings rather than errors when performing some casts.

For example, here is code that promotes a list of objects into a list of strings, if the list of objects contains only strings, and throws a class cast exception otherwise:

 class Promote {   public static List<String> promote(List<Object> objs) {     for (Object o : objs)       if (!(o instanceof String))         throw new ClassCastException();     return (List<String>)(List<?>)objs; // unchecked cast   }   public static void main(String[] args) {     List<Object> objs1 = Arrays.<Object>asList("one","two");     List<Object> objs2 = Arrays.<Object>asList(1,"two");     List<String> strs1 = promote(objs1);     assert (List<?>)strs1 == (List<?>)objs1;     boolean caught = false;     try {       List<String> strs2 = promote(objs2);     } catch (ClassCastException e) { caught = true; }     assert caught;   } } 

The method promote loops over the list of objects and throws a class cast exception if any object is not a string. Hence, when the last line of the method is reached, it is safe to cast the list of objects to a list of strings.

But the compiler cannot deduce this, so the programmer must use an unchecked cast. It is illegal to cast a list of objects to a list of strings, so the cast must take place in two steps. First, cast the list of objects into a list of wildcard type; this cast is safe. Second, cast the list of wildcard type into a list of strings; this cast is permitted but generates an unchecked warning:

 % javac -Xlint:unchecked Promote.java Promote.java:7: warning: [unchecked] unchecked cast found   : java.util.List required: java.util.List<java.lang.String>     return (List<String>)(List<?>)objs; // unchecked cast                          ^ 1 warning 

The test code applies the method to two lists, one containing only strings (so it succeeds) and one containing an integer (so it raises an exception). In the first assertion, to compare the object list and the string list, we must first cast both to the type List<?> (this cast is safe), because attempting to compare a list of objects with a list of strings raises a type error.

Exactly the same technique can be used to promote a raw list to a list of strings if the raw list contains only strings. This technique is important for fitting together legacy and generic code, and is one of the chief reasons for using erasure to implement generics. A related technique is discussed in Section 8.1.

Another example of the use of unchecked casts to interface legacy and generic code occurred in Section 5.4.1, where we needed an unchecked cast to the element type (E) to make the type of the value returned by the legacy add method match its generic signature.

You should minimize the number of unchecked casts in your code, but sometimes, as in the case above, they cannot be avoided. In this book, we follow the convention that we always place the comment unchecked cast on the line containing the cast, to document that this is an intentional workaround rather than an unintended slip; and we recommend you do the same. It is important to put the comment on the same line as the cast, so that when scanning the warnings issued by the compiler it is easy to confirm that each line contains the comment. If it does not, then you should regard the warning as equivalent to an error!

If a method deliberately contains unchecked casts, you may wish to precede it with the annotation @SuppressWarnings("unchecked") in order to avoid spurious warnings. We saw an application of this technique in Section 5.4.1.

As another example of the use of unchecked casts, in Section 6.5 we will see code that uses an unchecked cast from type Object[] to type T[]. Because of the way the object array is created, it is, in fact, guaranteed that the array will always have the correct type.

Unchecked casts in C (and in its descendants C++ and C#) are much more dangerous than unchecked casts in Java. Unlike C, the Java runtime guarantees important security properties even in the presence of unchecked casts; for instance, it is never permitted to access an array with an index outside of the array bounds. Nonetheless, unchecked casts in Java are a workaround that should be used with caution.




Java Generics and Collections
Java Generics and Collections
ISBN: 0596527756
EAN: 2147483647
Year: 2006
Pages: 136

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