Java中的桥方法是合成方法(synthetic methods),合成方法对于实现Java语言特征是必需的。最广为人知的例子就是协变返回类型和泛型中的案例,在泛型中案例基方法的参数被擦除后与实际被调用的方法不同时会使用到桥方法。
首先来看一个例子:
public class BridgeMethodOne { public static class BMOne<T> { public T getT() { return null; } } public static class BMTwo extends BMOne<String> { public String getT() { return null; } } }
事实上这仅仅是一个协变返回类型的例子,在类型擦除之后看起来和下面的片段类似:
public class BridgeMethodOne { public static class BMOne { public Object getT() { return null; } } public static class BMTwo extends BMOne { public String getT() { return null; } } }
然后在编译反编译之后,BMTwo将和下面类似:
public static class learn.generic.BridgeMethodOne$BMTwo extends learn.generic.BridgeMethodOne$BMOne {
.…
// Method descriptor #15 ()Ljava/lang/String;
// Stack: 1, Locals: 1
public java.lang.String getT();
0 aconst_null
1 areturn
// Method descriptor #16 ()Ljava/lang/Object;
// Stack: 1, Locals: 1
public bridge synthetic java.lang.Object getT();
0 aload_0 [this]
1 invokevirtual learn.generic.BridgeMethodOne$BMTwo.getT() : java.lang.String [17]
4 areturn
}
在上面可以看见有一个新的合成方法“java.lang.Object getT()”,这是源代码中没有的。这个方法作为一个桥方法,它所做的全部工作就是将调用代理到“java.lang.String getT()”。因为在JVM里,方法的返回类型是方法签名的一部分,而创建桥方法是实现协变返回类型的方式,因此编译器必须这么做。
现在再来看下面这个泛型指定的例子:
public class BridgeMethodTwo { public static class BMOne<T> { public T getT(T args) { return args; } } public static class BMTwo extends BMOne<String> { public String getT(String args) { return args; } } }
在编译之后,BMTwo将被转换成如下:
public static class learn.generic.BridgeMethodTwo$BMTwo extends learn.generic.BridgeMethodTwo$BMOne {
public java.lang.String getT(java.lang.String args);
0 aload_1 [args]
1 areturn
// Method descriptor #18 (Ljava/lang/Object;)Ljava/lang/Object;
// Stack: 2, Locals: 2
public bridge synthetic java.lang.Object getT(java.lang.Object arg0);
0 aload_0 [this]
1 aload_1 [arg0]
2 checkcast java.lang.String [19]
5 invokevirtual learn.generic.BridgeMethodTwo$BMTwo.getT(java.lang.String) : java.lang.String [21]
8 areturn
}
在这里,桥方法重写了基类BMOne,它不仅做了有参数的调用,同时还执行了到“java.lang.String”的类型转换。这意味着在执行下面的代码忽略编译器的“uncheck”警告时,桥方法将抛出ClassCastException异常。
public static void main(String[] args) { BMOne one = new BMTwo(); one.getT(new Object()); }
Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
所列的两个例子是桥方法使用中最广为人知的,但除此之外,桥方法至少还有一个用武之地,那就是被用来改变基类方法的可见性。试着查看下面的例子并尝试着猜测编辑器需要在什么地方创建桥方法。
public class BridgeMethodThree { static class ClassA { public void foo() { } } public static class ClassB extends ClassA { } public static class ClassC extends ClassA { public void foo() { } } }
如果查看ClassB编译后的文件,可以发现发现:
public static class learn.generic.BridgeMethodThree$ClassB extends learn.generic.BridgeMethodThree$ClassA {
public BridgeMethodThree$ClassB();
0 aload_0 [this]
1 invokespecial learn.generic.BridgeMethodThree$ClassA() [8]
4 return
public bridge synthetic void foo();
0 aload_0 [this]
1 invokespecial learn.generic.BridgeMethodThree$ClassA.foo() : void [15]
4 return
}
因为ClassA是包级别限制的,不能被包外访问,而ClassB是公共的,而所有继承的方法必须能够被包外所访问,因此编译器需要桥方法。注意,由于ClassC重写了“foo”方法,不再需要桥方法来增加可见性。
也许还有一些其它地方使用了桥方法,但没有关于这些的源信息。同样,也没有桥方法的定义,虽然从上述的例子已经看出来并可以猜到大致代表什么,但JLS里却没有明确的说明。尽管isBridge是java1.5之后反射API了的公共方法,JVM和JLS没有关于其的确切定义和编译器在何时、如何使用桥方法的定义。一般情况下,当一个类实现了一个参数化的接口或是继承了一个参数化的类时,需要引入桥方法。
英文参考:http://happyenjoylife.iteye.com/blog/1153964