Thursday, February 16, 2012

Java Bridge Methods

Before diving into the details about bridge methods let's swim a little and get familiar with one of the features introduced in Java 5, covariant return types. This feature basically allows an overriding method to return a more specialized type than the method it overrides. For example:
class Node { }

class TextNode extends Node { }

class NodeFactory {
    public Node makeNode() {
        return new Node();
    }
}

class TextNodeFactory extends NodeFactory {
    public TextNode makeNode() {
        return new TextNode();
    }
}

TextNodeFactory class overrides makeNode method and is allowed to return a TextNode because TextNode is a specialization of Node. So far everything looks good. But let's see how the compilation of TextNodeFactory looks like (courtesy of javap -c TextNodeFactory):
class TextNodeFactory extends NodeFactory {
TextNodeFactory();
  Code:
   0: aload_0
   1: invokespecial #1; //Method NodeFactory."<init>":()V
   4: return

public TextNode makeNode();
  Code:
   0: new #2; //class TextNode
   3: dup
   4: invokespecial #3; //Method TextNode."<init>":()V
   7: areturn

public Node makeNode();
  Code:
   0: aload_0
   1: invokevirtual #4; //Method makeNode:()LTextNode;
   4: areturn
}
It seems that the compiler generated another method public Node makeNode() that simply forwards the call to our method public TextNode makeNode(). Using reflection we find out that the method is a synthetic method, which is ok because Java Language Specification states that: Any constructs introduced by the compiler that do not have a corresponding construct in the source code must be marked as synthetic, except for default constructors and the class initialization method. We also find out that the method is a bridge method.

What are bridge methods?

Bridge methods are methods generated by the compiler in order to ensure correct overriding. They are needed because at the JVM level the method return type is part of the internal signature of the method. This means that the bridge method is the one that overrides NodeFactory's public Node makeNode() method.

Now let's modify our initial example and introduce generics.
interface Node { }

class TextNode implements Node { }

class NodeFactory<T extends Node> {

    public T makeNode() {
        return null;
    }

    public void releaseNode(T node) {
    }
}

class TextNodeFactory extends NodeFactory<TextNode> {

    public TextNode makeNode() {
        return new TextNode();
    }

    public void releaseNode(TextNode node) {
    }
}
After compilation NodeFactory will look like this:
class NodeFactory extends java.lang.Object{
NodeFactory();
  Code:
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."<init>":()V
   4: return

public Node makeNode();
  Code:
   0: aconst_null
   1: areturn

public void releaseNode(Node);
  Code:
   0: return

}
You can clearly see the effects of type erasure. The type information is lost and the lower bound type (i.e. Node) is used instead. Now let's take a look at TextNodeFactory.
class TextNodeFactory extends NodeFactory{
TextNodeFactory();
  Code:
   0: aload_0
   1: invokespecial #1; //Method NodeFactory."<init>":()V
   4: return

public TextNode makeNode();
  Code:
   0: new #2; //class TextNode
   3: dup
   4: invokespecial #3; //Method TextNode."<init>":()V
   7: areturn

public void releaseNode(TextNode);
  Code:
   0: return

public void releaseNode(Node);
  Code:
   0: aload_0
   1: aload_1
   2: checkcast #2; //class TextNode
   5: invokevirtual #4; //Method releaseNode:(LTextNode;)V
   8: return

public Node makeNode();
  Code:
   0: aload_0
   1: invokevirtual #5; //Method makeNode:()LTextNode;
   4: areturn

}
In this case the compiler generates 2 bridge methods. The first one public Node makeNode() is just another example of covariant return types. The second one public void releaseNode(Node) overrides the method with the same internal signature from the NodeFactory base class. It also makes sure its argument has the correct type before forwarding the call to our method. This is a safeguard for the case when one makes an unsafe call, like this:
((NodeFactory)textNodeFactory).releaseNode(new Node() { });
and the result will be a java.lang.ClassCastException.

There are probably other cases where bridge methods are used. Besides the fact the Method#isBridge() is part of the public API and the flag is defined in class file format there is no other "official" source of information related to bridge methods which leads me to the conclusion that bridge methods are a JVM implementation detail that has leaked.

No comments:

Post a Comment