Java 桥接方法(Bridge Method)


什么是「桥接方法」,下面来从两个例子中体会一下。

重写方法的返回类型是其父类返回类型的子类型

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(桥接方法)

发布了143 篇原创文章 · 获赞 196 · 访问量 77万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 精致技术 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览