`

Java中bridge方法探秘

    博客分类:
  • Java
 
阅读更多
 
今天在学习郑雨迪老师的《深入拆解 Java 虚拟机》课程,了解Java编译器在一些特殊情况下,会自动创建桥接方法,特在此实践记录一下。

 

bridge方法是由Java编译器自动生成的,所以在源代码中无法找到bridge关键字。那么在什么情况下,Java编译器会生成bridge方法呢?

 

1、防止编译出错:以具体类型继承自一个泛型类,同时被继承的泛型类包含了泛型方法

 

abstract class A<T> {
    public abstract T method1(T arg);
    public abstract T method2();
}

class B extends A<String> {
    public String method1(String arg) {
       return arg;
    }
    public String method2() {
       return "abc";
    }
}

class C<T> extends A<T> {
    public T method1(T arg) {
       return arg;
    }
   
    public T method2() {
       return null;
    }
}

  根据上述代码编译可以得到A、B、C三个类的class文件,分别通过javap -v 指令进行反编译,得到如下结果:

 

 

Classfile /D:/A.class
  Last modified 2018-10-24; size 377 bytes
  MD5 checksum d9683befc4ab4dab2179f0122aae8c9d
  Compiled from "Test.java"
abstract class A<T extends java.lang.Object> extends java.lang.Object
  minor version: 0
  major version: 52
  flags: ACC_SUPER, ACC_ABSTRACT
Constant pool:
   #1 = Methodref          #3.#18         // java/lang/Object."<init>":()V
   #2 = Class              #19            // A
   #3 = Class              #20            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               method1
   #9 = Utf8               (Ljava/lang/Object;)Ljava/lang/Object;
  #10 = Utf8               Signature
  #11 = Utf8               (TT;)TT;
  #12 = Utf8               method2
  #13 = Utf8               ()Ljava/lang/Object;
  #14 = Utf8               ()TT;
  #15 = Utf8               <T:Ljava/lang/Object;>Ljava/lang/Object;
  #16 = Utf8               SourceFile
  #17 = Utf8               Test.java
  #18 = NameAndType        #4:#5          // "<init>":()V
  #19 = Utf8               A
  #20 = Utf8               java/lang/Object
{
  A();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public abstract T method1(T);
    descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
    flags: ACC_PUBLIC, ACC_ABSTRACT
    Signature: #11                          // (TT;)TT;

  public abstract T method2();
    descriptor: ()Ljava/lang/Object;
    flags: ACC_PUBLIC, ACC_ABSTRACT
    Signature: #14                          // ()TT;
}
Signature: #15
SourceFile: "Test.java"

 

Classfile /D:/B.class
  Last modified 2018-10-24; size 580 bytes
  MD5 checksum 41ce932c4e2bc9ef13bda9d090ce6ef9
  Compiled from "Test.java"
class B extends A<java.lang.String>
  minor version: 0
  major version: 52
  flags: ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#22         // A."<init>":()V
   #2 = String             #23            // abc
   #3 = Methodref          #6.#24         // B.method2:()Ljava/lang/String;
   #4 = Class              #25            // java/lang/String
   #5 = Methodref          #6.#26         // B.method1:(Ljava/lang/String;)Ljava/lang/String;
   #6 = Class              #27            // B
   #7 = Class              #28            // A
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               method1
  #13 = Utf8               (Ljava/lang/String;)Ljava/lang/String;
  #14 = Utf8               method2
  #15 = Utf8               ()Ljava/lang/String;
  #16 = Utf8               ()Ljava/lang/Object;
  #17 = Utf8               (Ljava/lang/Object;)Ljava/lang/Object;
  #18 = Utf8               Signature
  #19 = Utf8               LA<Ljava/lang/String;>;
  #20 = Utf8               SourceFile
  #21 = Utf8               Test.java
  #22 = NameAndType        #8:#9          // "<init>":()V
  #23 = Utf8               abc
  #24 = NameAndType        #14:#15        // method2:()Ljava/lang/String;
  #25 = Utf8               java/lang/String
  #26 = NameAndType        #12:#13        // method1:(Ljava/lang/String;)Ljava/lang/String;
  #27 = Utf8               B
  #28 = Utf8               A
{
  B();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method A."<init>":()V
         4: return
      LineNumberTable:
        line 6: 0

  public java.lang.String method1(java.lang.String);
    descriptor: (Ljava/lang/String;)Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1
         1: areturn
      LineNumberTable:
        line 8: 0

  public java.lang.String method2();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: ldc           #2                  // String abc
         2: areturn
      LineNumberTable:
        line 11: 0

  public java.lang.Object method2();
    descriptor: ()Ljava/lang/Object;
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokevirtual #3                  // Method method2:()Ljava/lang/String;
         4: areturn
      LineNumberTable:
        line 6: 0

  public java.lang.Object method1(java.lang.Object);
    descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: checkcast     #4                  // class java/lang/String
         5: invokevirtual #5                  // Method method1:(Ljava/lang/String;)Ljava/lang/String;
         8: areturn
      LineNumberTable:
        line 6: 0
}
Signature: #19                          // LA<Ljava/lang/String;>;
SourceFile: "Test.java"

 

Classfile /D:/C.class
  Last modified 2018-10-24; size 416 bytes
  MD5 checksum eec9e342b1c9698264f2d7d94f8dec7c
  Compiled from "Test.java"
class C<T extends java.lang.Object> extends A<T>
  minor version: 0
  major version: 52
  flags: ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#18         // A."<init>":()V
   #2 = Class              #19            // C
   #3 = Class              #20            // A
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               method1
   #9 = Utf8               (Ljava/lang/Object;)Ljava/lang/Object;
  #10 = Utf8               Signature
  #11 = Utf8               (TT;)TT;
  #12 = Utf8               method2
  #13 = Utf8               ()Ljava/lang/Object;
  #14 = Utf8               ()TT;
  #15 = Utf8               <T:Ljava/lang/Object;>LA<TT;>;
  #16 = Utf8               SourceFile
  #17 = Utf8               Test.java
  #18 = NameAndType        #4:#5          // "<init>":()V
  #19 = Utf8               C
  #20 = Utf8               A
{
  C();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method A."<init>":()V
         4: return
      LineNumberTable:
        line 15: 0

  public T method1(T);
    descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1
         1: areturn
      LineNumberTable:
        line 17: 0
    Signature: #11                          // (TT;)TT;

  public T method2();
    descriptor: ()Ljava/lang/Object;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aconst_null
         1: areturn
      LineNumberTable:
        line 21: 0
    Signature: #14                          // ()TT;
}
Signature: #15                          // <T:Ljava/lang/Object;>LA<TT;>;
SourceFile: "Test.java"

 

 

 

可以看到B中生成了两个bridge方法(B.class的#72和#83的ACC_BRIDGE修饰符),而C中并没有。事实上,由于Java中存在泛型擦除机制,因而在编译A类的时候,它里面定义的方法都是以Object来表示的,如果没有bridge方法,B类根本没有办法覆盖A类中的abstract方法,也就无法通过编译。而C类由于在编译时所有的泛型也都是通过Object类表示,所以它覆盖了A类中的abstract方法,也就不需要再生成bridge方法了。

 

ps:若A中泛型形式为 T extend XXX,则泛型擦除后以XXX表示。

 

 

事实上,B类中的bridge方法在调用上也有一些区别:

 

public class MainTest {
	public static void main(String[] args) {
		B b = new B();
		b.method1("abc");

		A<String> a = new B();
		a.method1("abc");
	}
}

 编译该类,然后再使用java -v指令反编译,得到如下结果:

 

 

Classfile /D:/MainTest.class
  Last modified 2018-10-24; size 447 bytes
  MD5 checksum 96102fe169c54abdab88af079ff0aea1
  Compiled from "MainTest.java"
public class MainTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #8.#17         // java/lang/Object."<init>":()V
   #2 = Class              #18            // B
   #3 = Methodref          #2.#17         // B."<init>":()V
   #4 = String             #19            // abc
   #5 = Methodref          #2.#20         // B.method1:(Ljava/lang/String;)Ljava/lang/String;
   #6 = Methodref          #21.#22        // A.method1:(Ljava/lang/Object;)Ljava/lang/Object;
   #7 = Class              #23            // MainTest
   #8 = Class              #24            // java/lang/Object
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               main
  #14 = Utf8               ([Ljava/lang/String;)V
  #15 = Utf8               SourceFile
  #16 = Utf8               MainTest.java
  #17 = NameAndType        #9:#10         // "<init>":()V
  #18 = Utf8               B
  #19 = Utf8               abc
  #20 = NameAndType        #25:#26        // method1:(Ljava/lang/String;)Ljava/lang/String;
  #21 = Class              #27            // A
  #22 = NameAndType        #25:#28        // method1:(Ljava/lang/Object;)Ljava/lang/Object;
  #23 = Utf8               MainTest
  #24 = Utf8               java/lang/Object
  #25 = Utf8               method1
  #26 = Utf8               (Ljava/lang/String;)Ljava/lang/String;
  #27 = Utf8               A
  #28 = Utf8               (Ljava/lang/Object;)Ljava/lang/Object;
{
  public MainTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #2                  // class B
         3: dup
         4: invokespecial #3                  // Method B."<init>":()V
         7: astore_1
         8: aload_1
         9: ldc           #4                  // String abc
        11: invokevirtual #5                  // Method B.method1:(Ljava/lang/String;)Ljava/lang/String;
        14: pop
        15: new           #2                  // class B
        18: dup
        19: invokespecial #3                  // Method B."<init>":()V
        22: astore_2
        23: aload_2
        24: ldc           #4                  // String abc
        26: invokevirtual #6                  // Method A.method1:(Ljava/lang/Object;)Ljava/lang/Object;
        29: pop
        30: return
      LineNumberTable:
        line 4: 0
        line 5: 8
        line 7: 15
        line 8: 23
        line 9: 30
}
SourceFile: "MainTest.java"

 可以看见,引用类型为B的B实例,直接调用B类中的method1(java.lang.String)方法,而引用类型为A的B实例,则是调用B类中的桥接方法method1(java.lang.Object)方法,在桥接方法中再调用method1(java.lang.String)方法,即B.class的#89。

 

 2、实现Java语言重写语义:子类方法返回值是父类相应方法返回值的子类型

在Java语言中,方法重写要求父类方法和子类方法的方法名和参数类型相同;而在JVM中,方法重写的判断依据为方法名、方法描述符相同,而方法描述符包含参数类型和返回值类型。因此,Java语言中表现为重写语义的代码,在JVM中可能就是非重写的情况。针对这种情况,Java编译器会通过生成bridge方法来实现Java语言中的重写语义。

 

interface Customer {
  boolean isVIP();
}
 
class Merchant {
  public Number actionPrice(double price, Customer customer) {
    // ...
    return 0;
  }
}
 
class NaiveMerchant extends Merchant {
  @Override
  public Double actionPrice(double price, Customer customer) {
    // ...
    return 0D;
  }
}

 根据上述代码编译可以得到Customer、Merchant、NaiveMerchant三个类的class文件,分别通过javap -v 指令进行反编译,得到如下结果:

Classfile /D:/Customer.class
  Last modified 2018-10-24; size 115 bytes
  MD5 checksum a61b62d5f33f0fe496f31d7d16fd671e
  Compiled from "Test.java"
interface Customer
  minor version: 0
  major version: 52
  flags: ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
  #1 = Class              #7              // Customer
  #2 = Class              #8              // java/lang/Object
  #3 = Utf8               isVIP
  #4 = Utf8               ()Z
  #5 = Utf8               SourceFile
  #6 = Utf8               Test.java
  #7 = Utf8               Customer
  #8 = Utf8               java/lang/Object
{
  public abstract boolean isVIP();
    descriptor: ()Z
    flags: ACC_PUBLIC, ACC_ABSTRACT
}
SourceFile: "Test.java"

 

Classfile /D:/Merchant.class
  Last modified 2018-10-24; size 345 bytes
  MD5 checksum db00af1d8ffcecafbf260caa21213b49
  Compiled from "Test.java"
class Merchant
  minor version: 0
  major version: 52
  flags: ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#13         // java/lang/Object."<init>":()V
   #2 = Methodref          #14.#15        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #3 = Class              #16            // Merchant
   #4 = Class              #17            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               actionPrice
  #10 = Utf8               (DLCustomer;)Ljava/lang/Number;
  #11 = Utf8               SourceFile
  #12 = Utf8               Test.java
  #13 = NameAndType        #5:#6          // "<init>":()V
  #14 = Class              #18            // java/lang/Integer
  #15 = NameAndType        #19:#20        // valueOf:(I)Ljava/lang/Integer;
  #16 = Utf8               Merchant
  #17 = Utf8               java/lang/Object
  #18 = Utf8               java/lang/Integer
  #19 = Utf8               valueOf
  #20 = Utf8               (I)Ljava/lang/Integer;
{
  Merchant();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0

  public java.lang.Number actionPrice(double, Customer);
    descriptor: (DLCustomer;)Ljava/lang/Number;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=4, args_size=3
         0: iconst_0
         1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         4: areturn
      LineNumberTable:
        line 8: 0
}
SourceFile: "Test.java"

 

Classfile /D:/NaiveMerchant.class
  Last modified 2018-10-24; size 429 bytes
  MD5 checksum c466aec3c5469abf26cdb273eb30d02c
  Compiled from "Test.java"
class NaiveMerchant extends Merchant
  minor version: 0
  major version: 52
  flags: ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#15         // Merchant."<init>":()V
   #2 = Methodref          #16.#17        // java/lang/Double.valueOf:(D)Ljava/lang/Double;
   #3 = Methodref          #4.#18         // NaiveMerchant.actionPrice:(DLCustomer;)Ljava/lang/Double;
   #4 = Class              #19            // NaiveMerchant
   #5 = Class              #20            // Merchant
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               actionPrice
  #11 = Utf8               (DLCustomer;)Ljava/lang/Double;
  #12 = Utf8               (DLCustomer;)Ljava/lang/Number;
  #13 = Utf8               SourceFile
  #14 = Utf8               Test.java
  #15 = NameAndType        #6:#7          // "<init>":()V
  #16 = Class              #21            // java/lang/Double
  #17 = NameAndType        #22:#23        // valueOf:(D)Ljava/lang/Double;
  #18 = NameAndType        #10:#11        // actionPrice:(DLCustomer;)Ljava/lang/Double;
  #19 = Utf8               NaiveMerchant
  #20 = Utf8               Merchant
  #21 = Utf8               java/lang/Double
  #22 = Utf8               valueOf
  #23 = Utf8               (D)Ljava/lang/Double;
{
  NaiveMerchant();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method Merchant."<init>":()V
         4: return
      LineNumberTable:
        line 12: 0

  public java.lang.Double actionPrice(double, Customer);
    descriptor: (DLCustomer;)Ljava/lang/Double;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=3
         0: dconst_0
         1: invokestatic  #2                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
         4: areturn
      LineNumberTable:
        line 16: 0

  public java.lang.Number actionPrice(double, Customer);
    descriptor: (DLCustomer;)Ljava/lang/Number;
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=4, locals=4, args_size=3
         0: aload_0
         1: dload_1
         2: aload_3
         3: invokevirtual #3                  // Method actionPrice:(DLCustomer;)Ljava/lang/Double;
         6: areturn
      LineNumberTable:
        line 12: 0
}
SourceFile: "Test.java"

 可以看到NaiveMerchant中生成了一个bridge方法(NaiveMerchant.class的#58的ACC_BRIDGE修饰符),此时Java编译器生成的bridge方法是为了实现Java语言中的重写语义。

 

看下调用情况:

 

public class MainTest {  
    public static void main(String[] args) {  
        NaiveMerchant m1 = new NaiveMerchant();  
        m1.actionPrice(0D, null);  

        Merchant m2 = new NaiveMerchant();  
        m2.actionPrice(0D, null);  
    }  
}  
  编译该类,然后再使用java -v指令反编译,得到如下结果:

 

 

Classfile /D:/MainTest.class
  Last modified 2018-10-24; size 447 bytes
  MD5 checksum 1ef5eb90881c3b3da897da96ba57e894
  Compiled from "MainTest.java"
public class MainTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#16         // java/lang/Object."<init>":()V
   #2 = Class              #17            // NaiveMerchant
   #3 = Methodref          #2.#16         // NaiveMerchant."<init>":()V
   #4 = Methodref          #2.#18         // NaiveMerchant.actionPrice:(DLCustomer;)Ljava/lang/Double;
   #5 = Methodref          #19.#20        // Merchant.actionPrice:(DLCustomer;)Ljava/lang/Number;
   #6 = Class              #21            // MainTest
   #7 = Class              #22            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               SourceFile
  #15 = Utf8               MainTest.java
  #16 = NameAndType        #8:#9          // "<init>":()V
  #17 = Utf8               NaiveMerchant
  #18 = NameAndType        #23:#24        // actionPrice:(DLCustomer;)Ljava/lang/Double;
  #19 = Class              #25            // Merchant
  #20 = NameAndType        #23:#26        // actionPrice:(DLCustomer;)Ljava/lang/Number;
  #21 = Utf8               MainTest
  #22 = Utf8               java/lang/Object
  #23 = Utf8               actionPrice
  #24 = Utf8               (DLCustomer;)Ljava/lang/Double;
  #25 = Utf8               Merchant
  #26 = Utf8               (DLCustomer;)Ljava/lang/Number;
{
  public MainTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=3, args_size=1
         0: new           #2                  // class NaiveMerchant
         3: dup
         4: invokespecial #3                  // Method NaiveMerchant."<init>":()V
         7: astore_1
         8: aload_1
         9: dconst_0
        10: aconst_null
        11: invokevirtual #4                  // Method NaiveMerchant.actionPrice:(DLCustomer;)Ljava/lang/Double;
        14: pop
        15: new           #2                  // class NaiveMerchant
        18: dup
        19: invokespecial #3                  // Method NaiveMerchant."<init>":()V
        22: astore_2
        23: aload_2
        24: dconst_0
        25: aconst_null
        26: invokevirtual #5                  // Method Merchant.actionPrice:(DLCustomer;)Ljava/lang/Number;
        29: pop
        30: return
      LineNumberTable:
        line 3: 0
        line 4: 8
        line 6: 15
        line 7: 23
        line 8: 30
}
SourceFile: "MainTest.java"
 可以看见,引用类型为NaiveMerchant的NaiveMerchant实例,直接调用NaiveMerchant类中的public java.lang.Double actionPrice(double, Customer)方法,而引用类型为Merchant的NaiveMerchant实例,则是调用NaiveMerchant类中的桥接方法public java.lang.Number actionPrice(double, Customer)方法,在桥接方法中再调用public java.lang.Double actionPrice(double, Customer)方法,即NaiveMerchant.class的#64。

 

 

参考

1、Java中的Bridge方法

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics