Generic Code and the Virtual Machine Core Java

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 to 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.

The Retroweaver program rewrites class files so that they are compatible with older virtual machines. 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:

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<Gregorian- Calendar>, but erasure turns them all into raw Pair types. C++ NOTE: In this regard, Java generics are very different from C++ templates. C++ produces different types for each template instantiation, a phenomenon called “template code bloat.” Java does not suffer from this problem.

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:

You may wonder what happens if you switch the bounds: class Interval<Serializable& Comparable>. In that case, the raw type replaces T withSerializable, and the compiler inserts casts to Comparable when necessary. For efficiency, you should therefore put tagging interfaces (that is, interfaces without methods) at the end of the bounds list.

Translating Generic Expressions

When 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

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:

  • A call to the raw method Pair.getFirst
  • A cast of the returned Object to the Employee type

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 Methods

Type erasure also happens for generic methods. Programmers usually think of a generic such as

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:

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

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:

Our expectation is that the call to set Second 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

To see why this works, let us carefully follow the execution of the statement

The variable pair has declared type Pair<Date>, and that type only has a single method called setSecond, namely set Second (Object). The virtual machine calls that method on the object to which pair refers. That object is of type Date Interval. Therefore, the method DateInterval .set Second (Object) is called. That method is the synthesized bridge method. It calls DateInterval .set Second (Date), which is what we want.

Bridge methods can get even stranger. Suppose the DateInterval method also overrides the getSecond method:

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.

Bridge methods are not limited to generic types. We already noted in earlier sections that, starting with Java SE 5.0, it is legal for a method to specify a more restrictive return type when overriding another method. For example:

The Object.clone and Employee.clone methods are said to have covariant return types. Actually, the Employee class has two clone methods:

The synthesized bridge method calls the newly defined method. In summary, you need to remember these facts about translation of Java generics:

  • There are no generics in the virtual machines, only ordinary classes and methods.
  • All type parameters are replaced by their bounds.
  • Bridge methods are synthesized to preserve polymorphism.
  • Casts are inserted as necessary to preserve type safety.

Calling Legacy Code

Lots of Java code was written before Java SE 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 setLabel Table(Dictionary table)

Earlier, we used the following code to populate the label table:

In Java SE 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.

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 t a parameterized type variable, but of course you will get a warning. For example:

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 Java SE 5.0. In the worst case, your program will throw an exception. After you are done pondering the warning, you can use an annotation to make it disappear. The annotation must be placed before the method whose code generates the warning, like this:

Unfortunately, this annotation turns off checking for all code inside the method. It is a good idea to isolate potentially unsafe code into separate methods so that they can be reviewed more easily.

The Hashtable class is a concrete subclass of the abstract Dictionary class. Both Dictionary and Hashtable have been declared as “obsolete” ever since they were superseded by the Map interface and the HashMap class of Java SE 1.2. Apparently though, they are still alive and kicking. After all, the JSlider class was only added in Java SE 1.3. Didn’t its programmers know about the Map class by then? Does this make you hopeful that they are going to adopt generics in the near future? Well, that’s the way it goes with legacy code.

All rights reserved © 2018 Wisdom IT Services India Pvt. Ltd Protection Status

Core Java Topics