In the following sections, we discuss a number of restrictions that you need to consider when working with Java generics. Most of these restrictions are a consequence of type erasure. Primitive TypesYou cannot substitute a primitive type for a type parameter. Thus, there is no Pair<double>, only Pair<Double>. The reason is, of course, type erasure. After erasure, the Pair class has fields of type Object, and you can't use them to store double values. This is an annoyance, to be sure, but it is consistent with the separate status of primitive types in the Java language. It is not a fatal flaw there are only eight primitive types, and you can always handle them with separate classes and methods when wrapper types are not an acceptable substitute. Runtime Type InquiryObjects in the virtual machine always have a specific nongeneric type. Therefore, all type inquiries yield only the raw type. For example, if (a instanceof Pair<String>) // same as a instanceof Pair really only tests whether a is a Pair of any type. The same is true for the test if (a instanceof Pair<T>) // T is ignored or the cast Pair<String> p = (Pair<String>) a; // WARNING--can only test that a is a Pair To remind you of the risk, you will get a compiler warning whenever you use instanceof or cast expressions that involve generic types. In the same spirit, the getClass method always returns the raw type. For example, Pair<String> stringPair = . . .; Pair<Employee> employeePair = . . .; if (stringPair.getClass() == employeePair.getClass()) // they are equal The comparison yields true because both calls to getClass return Pair.class. ExceptionsYou can neither throw nor catch objects of a generic class. In fact, it is not even legal for a generic class to extend Throwable. For example, the following definition will not compile: public class Problem<T> extends Exception { /* . . . */ } // ERROR--can't extend Throwable You cannot use a type variable in a catch clause. For example, the following method will not compile:
However, it is ok to use type variables in exception specifications. The following method is legal:
ArraysYou cannot declare arrays of parameterized types, such as Pair<String>[] table = new Pair<String>(10); // ERROR What's wrong with that? After erasure, the type of table is Pair[]. You can convert it to Object[]: Object[] objarray = table; We discussed in Chapter 5 that an array remembers its component type and throws an ArrayStoreException if you try to store an element of the wrong type: objarray[0] = "Hello"; // ERROR--component type is Pair But erasure renders this mechanism ineffective for generic types. The assignment objarray[0] = new Pair<Employee>(); would pass the array store check but still result in a type error. For this reason, arrays of parameterized types are outlawed. TIP
Instantiation of Generic TypesYou cannot instantiate generic types. For example, the following Pair<T> constructor is illegal: public Pair() { first = new T(); second = new T(); } // ERROR Type erasure would change T to Object, and surely you don't want to call new Object(). Similarly, you cannot make a generic array: public <T> T[] minMax(T[] a) { T[] mm = new T[2]; . . . } // ERROR Type erasure would cause this method to always construct an array Object[2]. However, you can construct generic objects and arrays through reflection, by calling the Class.newInstance and Array.newInstance methods. Static ContextsYou cannot reference type variables in static fields or methods. For example, the following clever idea won't work:
If this could be done, then a program could declare a Singleton<Random> to share a random number generator and a Singleton<JFileChooser> to share a file chooser dialog. But it can't work. After type erasure there is only one Singleton class, and only one singleInstance field. For that reason, static fields and methods with type variables are simply outlawed. Clashes after ErasureIt is illegal to create conditions that cause clashes when generic types are erased. Here is an example. Suppose we add an equals method to the Pair class, like this: public class Pair<T> { public boolean equals(T value) { return first.equals(value) && second.equals(value); } . . . } Consider a Pair<String>. Conceptually, it has two equals methods: boolean equals(String) // defined in Pair<T> boolean equals(Object) // inherited from Object But the intuition leads us astray. The erasure of the method boolean equals(T) is boolean equals(Object) which clashes with the Object.equals method. The remedy is, of course, to rename the offending method. The generics specification cites another rule: "To support translation by erasure, we impose the restriction that a class or type variable may not at the same time be a subtype of two interface types which are different parameterizations of the same interface." For example, the following is illegal: class Calendar implements Comparable<Calendar> { . . . } class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar> { . . . } // ERROR GregorianCalendar would then implement both Comparable<Calendar> and Comparable<GregorianCalendar>, which are different parameterizations of the same interface. It is not clear what this restriction has to do with type erasure. The nongeneric version class Employee implements Comparable { . . . } class Manager extends Employee implements Comparable { . . . } is legal. |