The virtual machine does not have objects of generic types all objects belong to ordinary classes. An earlier version of the generics implementation was even able compile a program that uses generics into class files that executed on 1.0 virtual machines! This backward compatibility was only abandoned fairly late in the development for Java generics. If you use the Sun compiler to compile code that uses Java generics, the resulting class files will not execute on pre-5.0 virtual machines. NOTE
Whenever you define a generic type, a corresponding raw type is automatically provided. The name of the raw type is simply the name of the generic type, with the type parameters removed. The type variables are erased and replaced by their bounding types (or Object for variables without bounds.) For example, the raw type for Pair<T> looks like this: public class Pair { public Pair(Object first, Object second) { this.first = first; this.second = second; } public Object getFirst() { return first; } public Object getSecond() { return second; } public void setFirst(Object newValue) { first = newValue; } public void setSecond(Object newValue) { second = newValue; } private Object first; private Object second; } Because T is an unbounded type variable, it is simply replaced by Object. The result is an ordinary class, just as you might have implemented it before generics were added to the Java programming language. Your programs may contain different kinds of Pair, such as Pair<String> or Pair<GregorianCalendar>, but erasure turns them all into raw Pair types. C++ NOTE
The raw type replaces type variables with the first bound, or Object if no bounds are given. For example, the type variable in the class Pair<T> has no explicit bounds, hence the raw type replaces T with Object. Suppose we declare a slightly different type public class Interval<T extends Comparable & Serializable> implements Serializable { public Interval(T first, T second) { if (first.compareTo(second) <= 0) { lower = first; upper = second; } else { lower = second; upper = first; } } . . . private T lower; private T upper; } The raw type Interval looks like this: public class Interval implements Serializable { public Interval(Comparable first, Comparable second) { . . . } . . . private Comparable lower; private Comparable upper; } NOTE
Translating Generic ExpressionsWhen you program a call to a generic method, the compiler inserts casts when the return type has been erased. For example, consider the sequence of statements Pair<Employee> buddies = . . .; Employee buddy = buddies.getFirst(); The erasure of getFirst has return type Object. The compiler automatically inserts the cast to Employee. That is, the compiler translates the method call into two virtual machine instructions:
Casts are also inserted when you access a generic field. Suppose the first and second fields of the Pair class were public. (Not a good programming style, perhaps, but it is legal Java.) Then the expression Employee buddy = buddies.first; also has a cast inserted in the resulting byte codes. Translating Generic MethodsType erasure also happens for generic methods. Programmers usually think of a generic method such as public static <T extends Comparable> T min(T[] a) as a whole family of methods, but after erasure, only a single method is left: public static Comparable min(Comparable[] a) Note that the type parameter T has been erased, leaving only its bounding type Comparable. Erasure of method brings up a couple of complexities. Consider this example: class DateInterval extends Pair<Date> { public void setSecond(Date second) { if (second.compareTo(getFirst()) >= 0) super.setSecond(second); } . . . } A date interval is a pair of Date objects, and we'll want to override the methods to ensure that the second value is never smaller than the first. This class is erased to class DateInterval extends Pair // after erasure { public void setSecond(Date second) { . . . } . . . } Perhaps surprisingly, there is another setSecond method, inherited from Pair, namely, public void setSecond(Object second) This is clearly a different method because it has a parameter of a different type Object instead of Date. But it shouldn't be different. Consider this sequence of statements: DateInterval interval = new DateInterval(. . .); Pair<Date> pair = interval; // OK--assignment to superclass pair.setSecond(aDate); Our expectation is that the call to setSecond is polymorphic and that the appropriate method is called. Because pair refers to a DateInterval object, that should be DateInterval.setSecond. The problem is that the type erasure interferes with polymorphism. To fix this problem, the compiler generates a bridge method in the DateInterval class: public void setSecond(Object second) { setSecond((Date) second); } To see why this works, let us carefully follow the execution of the statement pair.setSecond(aDate) The variable pair has declared type Pair<Date>, and that type only has a single method called setSecond, namely setSecond(Object). The virtual machine calls that method on the object to which pair refers. That object is of type DateInterval. Therefore, the method DateInterval.setSecond(Object) is called. That method is the synthesized bridge method. It calls DateInterval.setSecond(Date), which is what we want. Bridge methods can get even stranger. Suppose the DateInterval method also overrides the getSecond method: class DateInterval extends Pair<Date> { public Date getSecond() { return (Date) super.getSecond().clone(); } . . . } In the erased type, there are two getSecond methods: Date getSecond() // defined in DateInterval Object getSecond() // defined in Pair You could not write Java code like that it would be illegal to have two methods with the same parameter types here, no parameters. However, in the virtual machine, the parameter types and the return type specify a method. Therefore, the compiler can produce bytecodes for two methods that differ only in their return type, and the virtual machine will handle this situation correctly. NOTE
In summary, you need to remember these facts about translation of Java generics:
Calling Legacy CodeLots of Java code was written before JDK 5.0. If generic classes could not interoperate with that code, they would probably not be widely used. Fortunately, it is straightforward to use generic classes together with their raw equivalents in legacy APIs. Let us look at a concrete example. To set the labels of a JSlider, you use the method void setLabelTable(Dictionary table) In Chapter 9, we used the following code to populate the label table: Dictionary<Integer, Component> labelTable = new Hashtable<Integer, Component>(); labelTable.put(0, new JLabel(new ImageIcon("nine.gif"))); labelTable.put(20, new JLabel(new ImageIcon("ten.gif"))); . . . slider.setLabelTable(labelTable); // WARNING NOTE
In JDK 5.0, the Dictionary and Hashtable classes were turned into a generic class. Therefore, we are able to form Dictionary<Integer, Component> instead of using a raw Dictionary. However, when you pass the Dictionary<Integer, Component> object to setLabelTable, the compiler issues a warning. Dictionary<Integer, Components> labelTable = . . .; slider.setLabelTable(labelTable); // WARNING After all, the compiler has no assurance about what the setLabelTable might do to the Dictionary object. That method might replace all the keys with strings. That breaks the guarantee that the keys have type Integer, and future operations may cause bad cast exceptions. There isn't much you can do with this warning, except ponder it and ask what the JSlider is likely going to do with this Dictionary object. In our case, it is pretty clear that the JSlider only reads the information, so we can ignore the warning. Now consider the opposite case, in which you get an object of a raw type from a legacy class. You can assign it to a parameterized type variable, but of course you will get a warning. For example, Dictionary<Integer, Components> labelTable = slider.getLabelTable(); // WARNING That's ok review the warning and make sure that the label table really contains Integer and Component objects. Of course, there never is an absolute guarantee. A malicious coder might have installed a different Dictionary in the slider. But again, the situation is no worse than it was before JDK 5.0. In the worst case, your program will throw an exception. It is unfortunate that you can't turn off the warnings after you reviewed them. It seems impractical to review every warning every time you recompile a source file. The Java language designers are planning to add a more flexible warning mechanism to a future version of the JDK. |