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.

Wednesday, June 2, 2010

My DeLorean Package

I'm tasked by Dr. Emmett Brown to help Marty McFly travel to different time periods successfully on Doc's time machine - DeLorean!

However, I must ensure DeLorean must at least hit the speed of 88 miles per hour (142 km/h) for the flux capacitor to kick-in, then a big ZAP and vanishes from sight.

Below is the source code:
package delorean;
import java.util.Date;

public class DeLorean {
private static final int MAX_SPEED_MPH = 88;

public boolean timeTravel(Date date, int speedMPH) {
System.out.println("Setting DeLorean target date " + date);

if (prepareTimeTravel(speedMPH)) {
System.out.println("Time travel was successful, we are now on " + date);
return true;
}

System.out.println("Time travel failed");
return false;
}

private boolean prepareTimeTravel(int speedMPH) {
// we need at least 88 mph or equivalent to 142 km/h
// to activate flux capacitor for time travel
if (speedMPH >= MAX_SPEED_MPH) {
System.out.println("Activating flux capacitor");
return true;
}

return false;
}
}


I also wrote a unit-test to ensure the flux capacitor activates once speed is at 88 mph during Marty's travel:

package delorean;
import java.util.Calendar;
import junit.framework.Assert;
import org.junit.Before;
import org.junit.Test;

public class DeLoreanTest {
private static Calendar future;
private DeLorean deLorean;

static {
future = Calendar.getInstance();
future.set(Calendar.YEAR, 2015);
future.set(Calendar.DAY_OF_MONTH, 21);
future.set(Calendar.MONTH, Calendar.OCTOBER);
}

@Before
public void init() {
deLorean = new DeLorean();
}

@Test
public void testTimeTravelSuccess() {
Assert.assertTrue(deLorean.timeTravel(future.getTime(), 88));
}

@Test
public void testTimeTravelFailed() {
Assert.assertFalse(deLorean.timeTravel(future.getTime(), 87));
}
}


Below is the result of the test:

Setting DeLorean target date Wed Oct 21 03:03:50 EST 2015
Activating flux capacitor
Time travel was successful, we are now on Wed Oct 21 03:03:50 EST 2015
Setting DeLorean target date Wed Oct 21 03:03:50 EST 2015
Time travel failed

Everything looks fine, but...


OK I admit I'm test infected and I really feel the urge to also test "prepareTimeTravel" method alone by itself. However, how am I suppose to do this when it's defined as a private method? Surely I can turn this to public but I'd rather keep this abstracted away from Marty as he doesn't need to know the nitty-gritty details between the car's acceleration and the flux capacitor. All he needs to know is to step on accelerator pedal to reach 88 miles per hour and *poof* he goes.



*Thinking, thinking, thinking...*



What about setting this method as package scope? This means "prepareTimeTravel" method will only be visible to classes of the same package, including DeLoreanTest class (same package name)^. This also ensure classes from other packages won't see this method. This sounds like what I'm looking for.


package delorean;
import java.util.Date;

public class DeLorean {
private static final int MAX_SPEED_MPH = 88;

public boolean timeTravel(Date date, int speedMPH) {
System.out.println("Setting DeLorean target date " + date);

if (prepareTimeTravel(speedMPH)) {
System.out.println("Time travel was successful, we are now on " + date);
return true;
}

System.out.println("Time travel failed");
return false;
}

boolean prepareTimeTravel(int speedMPH) {
// we need at least 88 mph or equivalent to 142 km/h
// to activate flux capacitor for time travel
if (speedMPH >= MAX_SPEED_MPH) {
System.out.println("Activating flux capacitor");
return true;
}

return false;
}
}



The updated unit-test looks like this:

package delorean;
import java.util.Calendar;
import junit.framework.Assert;
import org.junit.Before;
import org.junit.Test;

public class DeLoreanTest {
private static Calendar future;
private DeLorean deLorean;

static {
future = Calendar.getInstance();
future.set(Calendar.YEAR, 2015);
future.set(Calendar.DAY_OF_MONTH, 21);
future.set(Calendar.MONTH, Calendar.OCTOBER);
}

@Before
public void init() {
deLorean = new DeLorean();
}

@Test
public void testTimeTravelSuccess() {
Assert.assertTrue(deLorean.timeTravel(future.getTime(), 88));
}

@Test
public void testTimeTravelFailed() {
Assert.assertFalse(deLorean.timeTravel(future.getTime(), 87));
}

@Test
public void testPrepareTimeTravelSuccess() {
Assert.assertTrue(deLorean.prepareTimeTravel(88));
Assert.assertTrue(deLorean.prepareTimeTravel(89));
Assert.assertTrue(deLorean.prepareTimeTravel(90));
}

@Test
public void testPrepareTimeTravelFailed() {
Assert.assertFalse(deLorean.prepareTimeTravel(87));
Assert.assertFalse(deLorean.prepareTimeTravel(86));
}
}

Vroooom... ZAP - *poof*



^ When using Maven, DeLorean class sits under "src/main/java directory" and DeLoreanTest class sits under "src/test/java" directory. Both classes contains same package name, hence package-scope method "prepareTimeTravel" will be visible to test class.