Thursday, 11 December 2014

Java type erasure in practice

"Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead."

Yay! No overhead! Lets see how this erasure thingy works in practice. javap will let us peek at the generated code and see what actually is going on. We have:
>java -version 
java version "1.8.0_72"

Case 1
public <T> void process(T t) {}

We don't really care about the method body, just the generated method signatures, javap -s produces:
public <T> void process(T);
    descriptor: (Ljava/lang/Object;)V
In this basic case the parameter T is replaced by Object

Case 2

It is valid to use a "bounded type parameter" that is, to have an upper bound on the type T can assume. This is expressed by using extends which is sort of combined extends/implements like so:

public <T extends Serializable> void process1(T t) {}
Looking at the compiled code we see:
public <T extends java.io.Serializable> void process1(T);
    descriptor: (Ljava/io/Serializable;)V
Looks like the compiler is replacing T with the most specific super class/interface. It should be all good, after all, Java is single inheritance. Except that off course we can implement as many interfaces as we like, which is supported by the generics implementation, and so that we have

Case 3
public  <T extends Comparable & EventListener > void process2(T t) {}
Now the compiler has to choose, what would that be...
public <T extends java.lang.Comparable & java.util.EventListener> void process2(T);
    descriptor: (Ljava/lang/Comparable;)V
So Comparable won. Is this because of the ordering?

Case 4
public  <T extends EventListener & Comparable> void process3(T t) {}

and indeed, this time EventListener comes out on top:
  public <T extends java.util.EventListener & java.lang.Comparable > void process3(T);
    descriptor: (Ljava/util/EventListener;)V
Here is the complete java code:
public class Erasure {
    public <T> void process(T t) {}
    public <T extends Serializable> void process1(T t) {}
    public <T extends Comparable & EventListener> void process2(T t) {}
    public <T extends EventListener & Comparable> void process3(T t) {}
}

3 comments:

  1. Hi David,

    Your code is incorrect in case 3 and case 4. 'EventListener'/'Comparable' is seen as a second (unbounded) parameter, not related to the first parameter 'T'. Javap tells you about it:

    T extends java/lang/Comparable // <--- parameter 'T' that extends java.lang.Comparable
    EventListener extends java/lang/Object // <--- parameter 'EventListener' that extends java.lang.Object

    The correct signature would be:

    public void process3(T t) {}

    (Note '&')

    ReplyDelete
    Replies
    1. (angle brackets got consumed as a tag)

      The correct signature would be:

      public <T extends Comparable & EventListener> void process3(T t) {}

      (Note '&')

      Delete