Lies, damned lies and methods implementing specialized generic interfaces

You know that I’ve been spoiled by spending years in a land with reified generics. I’ve heard Daniel Spiewak, in an episode of “The Scalawags“, say that he prefers erased generics because they permit more freedom in the typesystem, and Martin Odersky also swears by them, so I’m not ready to condemn them yet. But they can sure tie one in knots.

Here’s a recount of a recent experience of mine, suitably distilled to its essentials. Follows javap disassembly for a completely boring pair of interface and implementation.

Compiled from "Interface.java"
public interface Interface {
  public abstract boolean toBoolean(java.lang.String);
}
Compiled from "Implementation.java"
public class Implementation implements Interface {
  public Implementation();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public boolean toBoolean(java.lang.String);
    Code:
       0: iconst_1
       1: ireturn
}

Surely you must be yawning by now. Let’s spice it up by introducing some Java 8-like base interface. toBoolean looks like Predicate#test, doesn’t it? But this time I’ll also show you the original Java, because you’re in for a surprise.

public interface Predicate<T> {
  public abstract boolean test(T);
}

public interface Interface2 extends Predicate<java.lang.String> {
}

public class Implementation2 implements Interface2 {
 public boolean test(String s) {
  return true;
 }
}
Compiled from "Implementation2.java"
public class Implementation2 implements Interface2 {
  public Implementation2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public boolean test(java.lang.String);
    Code:
       0: iconst_1
       1: ireturn

  public boolean test(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #2                  // class java/lang/String
       5: invokevirtual #3                  // Method test:(Ljava/lang/String;)Z
       8: ireturn
}

What the…? Apparently, the mere act of defining Interface2 as a specialization of Predicate, actually specialized the signature of test, so as to allow uses of “real” test(String). But, at the same time, the compiler injected an erased test that calls the specialized one. That’s how I found it. I was creating the implementation with Javassist, and the Javassist compiler did not perform this sleigh-of-hand. Which was OK until I tried to call the method and got an abstract method error. Not a very common sight, is it?

Oh, and you cannot do this explicitly with Java code, because trying to do so gives you an error.

OtherImplementation2b.java:2: error: name clash: test(Object) in OtherImplementation2b and test(T) in Predicate have the same erasure, yet neither overrides the other
 public boolean test(Object o) {
                ^
  where T is a type-variable:
    T extends Object declared in interface Predicate
1 error

boolean test(java.lang.Object) should have been final, but isn’t. Even so, the aforementioned error does not let you override it, anyway. You can only override the specialized method.

Ultimately, it is not just compilers for “other” languages who need to jump into hoops to get around the limitations of Generics. This is a case of the Java compiler itself jumping through hoops. Remember that the Javassist-created class compiled fine and was loaded fine, but the error stroke at runtime. Beware!

UPDATE: These phantom methods are called “bridge” methods, and they are utilized in all places where the typesystem needs to express things the JVM cannot. They sport the ACC_BRIDGE flag.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s