Sunday, June 6, 2010

Java Generics and Synthetic Bridge Method

I'll take you to a Java-landia magical mystery tour, this time it's got something to do with generics.

Take a look at the example below.

MeanMeter.java:


public interface MeanMeter < T > {
boolean isMean(T t);
}


Compile this class and run "javap -c MeanMeter" produces:


Compiled from "MeanMeter.java"
public interface MeanMeter{
public abstract boolean isMean(java.lang.Object);

}


What happened to our parameter type T?!

OK. Park that for now. Let's look at another example.

JimsCreation.java:


public class JimsCreation implements MeanMeter< JimsCreation.Garfield > {
public boolean isMean(Garfield garfield) {
return true;
}

public static void main(String[] args) {
JimsCreation jimsCreation = new JimsCreation();
jimsCreation.isMean(new Garfield());
}

public static class Garfield {
}
}


Now, take a deep breath. Compile then run "javap -p JimsCreation" from command line.

Below is the result:


Compiled from "JimsCreation.java"
public class JimsCreation extends java.lang.Object implements MeanMeter{
public JimsCreation();
public boolean isMean(JimsCreation$Garfield);
public static void main(java.lang.String[]);
public boolean isMean(java.lang.Object);
}


We got 2 "isMean" methods?!!! What's going on here?!

Rub your eyes. Yes, Javac gave us 2 methods with different argument type!

"Is this some kind of black magic?"


So what happened here was that the compiler tricked us by introducing a "bridge" method - another word for "smart-overloaded-method-created-by-javac". It cleverly erased our generic type "T" and replaced it with "java.lang.Object" in MeanMeter interface; it also inserted the said signature into JimsCreation class. This is because Java promised backward compatibility to support non-generic sources from pre-JDK5 days.

By typing "javap -c JimsCreation" produces:


Compiled from "JimsCreation.java"
public class JimsCreation extends java.lang.Object implements MeanMeter{
public JimsCreation();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."< init >":()V
4: return

public boolean isMean(JimsCreation$Garfield);
Code:
0: iconst_1
1: ireturn

public static void main(java.lang.String[]);
Code:
0: new #2; //class JimsCreation
3: dup
4: invokespecial #3; //Method "< init >":()V
7: astore_1
8: aload_1
9: new #4; //class JimsCreation$Garfield
12: dup
13: invokespecial #5; //Method JimsCreation$Garfield."< init >":()V
16: invokevirtual #6; //Method isMean:(LJimsCreation$Garfield;)Z
19: pop
20: return

public boolean isMean(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: checkcast #4; //class JimsCreation$Garfield
5: invokevirtual #6; //Method isMean:(LJimsCreation$Garfield;)Z
8: ireturn

}


LO AND BEHOLD!!!"


Look at the last method at line 28! It casts the object type parameter to JimsCreation$Garfield^ at line 32 and invokes the overloaded method at line 33!

So the main question is what happens if we invoke JimsCreation.isMean(Garfield) method? Does it invoke the bridge method or the original method? Answer - it still invokes the original method. See line 24.


^JimsCreation$Garfield represents Garfield as static class to JimsCreation.

1 comment:

  1. That is interesting...

    I have heard that the Java 5 & Java 6 generics code compiles into old Java Byte Code for backwards compatability. So if you were to use generics and compile some code in JDK > 5, then you can still run it on some JRE 4.x environment.

    But that is only what I have heard and have not taken the effort to finding out the bytecode generation as you guys have done.

    Very interesting tho!!!

    ReplyDelete