什么是「桥接方法」,下面来从两个例子中体会一下。
重写方法的返回类型是其父类返回类型的子类型
public class Merchant {
public Number actionPrice(double price) {
return price * 0.8;
}
}
public class NaiveMerchant extends Merchant {
@Override
public Double actionPrice(double price) {
return 0.9 * price;
}
public static void main(String[] args) {
Merchant merchant = new NaiveMerchant();
// price 必须定义成 Number 类型
Number price = merchant.actionPrice(40);
System.out.println(price);
}
}
javac Merchant.java NaiveMerchant.java
# 反编译字节码
javap -v -c NaiveMerchant
查看 NaiveMerchant.class 反编译后字节码:
public java.lang.Double actionPrice(double);
descriptor: (D)Ljava/lang/Double;
flags: ACC_PUBLIC
Code:
stack=4, locals=3, args_size=2
0: dload_1
1: ldc2_w #2 // double 0.9d
4: dmul
5: invokestatic #4 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
8: areturn
LineNumberTable:
line 13: 0
// 桥接方法
public java.lang.Number actionPrice(double);
descriptor: (D)Ljava/lang/Number;
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=3, locals=3, args_size=2
0: aload_0
1: dload_1
2: invokevirtual #12 // Method actionPrice:(D)Ljava/lang/Double;
5: areturn
LineNumberTable:
line 9: 0
父类 Merchant 的 actionPrice 的返回值是 Number 类型,子类NaiveMerchant 重写 actionPrice 返回的值类似是 Double 类型,对于 Java 语言是重写的,但对于 Java 虚拟机解析来说却不是重写的,只有当两个方法的参数类型以及返回类型一致时,Java 虚拟机才会判定为重写,为了保持重写的语义,Java 编译器会在 NaiveMerchant 的字节码文件中自动生成一个桥接方法来保证重写语义:
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
ACC_BRIDGE:表示这个一个桥接方法
ACC_SYNTHETIC:表示这个方法由编译器自动生成
public java.lang.Number actionPrice(double);
翻译桥接方法的字节码:
public Number actionPrice(double price) {
return this.actionPrice(price);
}
这个桥接方法会去调用 NaiveMerchant 本身重写的方法。
编译器通过插入桥接方法来保证重写的语义,从而 Java 虚拟机通过方法描述符(由方法的参数类型和返回类型构成)定位到具体方法,执行方法调用:
Merchant merchant = new NaiveMerchant();
// 这里实际上调用的则是桥接方法
Number price = merchant.actionPrice(40);
重写泛型方法生成桥接
子类在继承父类的一个泛型方法、或子类实现一个接口的泛型方法,编译器会在子类的 class 文件中自动生成桥接方法。
interface Customer {
String purchase();
}
class VIP implements Customer {
@Override
public String purchase() {
return "VIP First !";
}
}
class NOT_VIP implements Customer {
@Override
public String purchase() {
return "VIP First !";
}
}
abstract class MerchantOther<T extends Customer> {
public double actionPrice(double price, T customer) {
return price * 0.08;
}
}
class VIPOnlyMerchant extends MerchantOther<VIP> {
@Override
public double actionPrice(double price, VIP customer) {
return price * 0.07;
}
}
public class MethodFind {
public static void main(String[] args) {
}
}
反编译父类 MerchantOther 的字节码文件:
泛型 T 被换成了 Customer,方法签名是:
public double actionPrice(double price, Customer customer);
再看 VIPOnlyMerchant.class 的字节码:
子类的字节码中有一个编译器自动生成的桥接方法,这个桥接方法翻译成 Java 代码就是:
public double actionPrice(double price, Customer customer) {
return this.actionPrice(price, (VIP) customer);
}
桥接方法调用了子类重写的泛型方法,执行下面的代码:
public static void main(String[] args) {
MerchantOther merchantOther = new VIPOnlyMerchant();
// 调用实际的方法
merchantOther.actionPrice(80, new VIP());
// 调用的是桥接方法,出现 java.lang.ClassCastException 的异常
merchantOther.actionPrice(90, new NOT_VIP());
}
由于泛型擦除,父类 MerchantOther 的参数实际上是 Customer 类型,为了保证重写的语义,兼容 1.5(泛型是在 1.5 引入的) 之前的字节码文件,所以生成桥接方法。new NOT_VIP()
传入 Customer 类型的参数,编译器也不会发现错误。运行时调用了桥接方法,桥接方法中使用 VIP 进行强制类型转换,当参数类型不是 VIP 时,就会抛出类型转换异常。
参考:Effects of Type Erasure and Bridge Methods
Java Bridge Method 详解
Java中什么是bridge method(桥接方法)