Java中的Bridge方法

简介:

今天在Java中字节码的格式的时候,发现method_info中的access_flags中竟然定了ACC_BRIDGE的值。网上搜了一下,大概理解它的意思了,先记之。

 

首先是在什么情况下会生成bridge方法(2):

bridge method may be created by the compiler when extending a parameterized type whose methods have parameterized arguments.

这是在网上找到的有人贴出来的一段话,但是感觉这段话说的并不是很明白。首先bridge方式是由编译器产生的,因而在源代码中也没有bridge的关键字。然后只有在以具体类型继承自一个泛型类,同时被继承的泛型类包含了泛型方法。比如看以下的例子:

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;
    }
}

 

 

他们生成的.class文件如下:

A.class

abstract class org.levin.insidejvm.miscs.bridgemethod.A {

 public abstract java.lang.Object method1(java.lang.Object arg0);

 public abstract java.lang.Object method2();

}

B.class

class org.levin.insidejvm.miscs.bridgemethod.B extends org.levin.insidejvm.miscs.bridgemethod.A {

 public java.lang.String method1(java.lang.String arg);

    0 aload_1 [arg]

    1 areturn

 public java.lang.String method2();

    0 ldc <String "abc"> [20]

    2 areturn

 public bridge synthetic java.lang.Object method2();

    0 aload_0 [this]

    1 invokevirtual org.levin.insidejvm.miscs.bridgemethod.B.method2() : java.lang.String [23]

    4 areturn

  public bridge synthetic java.lang.Object method1(java.lang.Object arg0);

    0 aload_0 [this]

    1 aload_1 [arg0]

    2 checkcast java.lang.String [26]

    5 invokevirtual org.levin.insidejvm.miscs.bridgemethod.B.method1(java.lang.String) : java.lang.String [28]

    8 areturn

}

C.class

class org.levin.insidejvm.miscs.bridgemethod.C extends org.levin.insidejvm.miscs.bridgemethod.A {

  public java.lang.Object method1(java.lang.Object arg);

    0 aload_1 [arg]

    1 areturn

 public java.lang.Object method2();

    0 aconst_null

    1 areturn

}

可以看到B中生成了两个bridge方法,而C中则没有。事实上,由于Java中泛型有擦除的机制,因而在编译A类的时候,它里面定义的方法都是以Object类型来表示了,因而如果没有bridge方法,B类根本没有覆盖A类中的abstract方法。正因为有bridge方法的存在,才使得B类可以编译通过。而C类由于在编译时所有的泛型也都是通过Object类来表达的,因而它实现的也是A类中的abstract方法,因而不用再生成bridge方法了。

 

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

     public   static   void  main(String[] args) {
       B b 
=   new  B();
       b.method1(
" abc " );
       A
< String >  a  =   new  B();
       a.method1(
" abc " );
    }

 

这段方法的字节码如下:

     0 new org.levin.insidejvm.miscs.bridgemethod.B [16]

     3 dup

     4 invokespecial org.levin.insidejvm.miscs.bridgemethod.B() [18]

     7 astore_1 [b]

     8 aload_1 [b]

     9 ldc <String "abc"> [19]

    11 invokevirtual org.levin.insidejvm.miscs.bridgemethod.B.method1(java.lang.String) : java.lang.String [21]

    14 pop

    15 new org.levin.insidejvm.miscs.bridgemethod.B [16]

    18 dup

    19 invokespecial org.levin.insidejvm.miscs.bridgemethod.B() [18]

    22 astore_2 [a]

    23 aload_2 [a]

    24 ldc <String "abc"> [19]

    26 invokevirtual org.levin.insidejvm.miscs.bridgemethod.A.method1(java.lang.Object) : java.lang.Object [25]

    29 pop

    30 return

以上的代码可以看出b变量调用的method1(String)的方法,而a变量调用的却是method1(Object)方法。这种区别也正式因为bridge方法提供的支持才实现的。

 

事实上,bridge方法还会在另外一种情况下产生(2):

Java 1.4中,子类若要重写父类某个方法,那么子类的方法和父类的方法签名必须完全一致,包括方法名、参数类型以及返回值;而到Java 1.5中,该机制变成,如果子类中某个方法的方法名和参数类型和父类某方法一致,并且子类该方法的返回值是父类相应方法返回值的类型或其子类型,那么该子类方法也可以重写父类中相应的方法。参看以下例子:

class E {
   
}
 
class F extends E {
   
}
 
class X {
    
public E getE() {
       
return new E();
    }
}
 
class Y extends X {
    @Override
    
public F getE() {
       
return new F();
    }
}

 

以上代码是可以编译通过的。让我们再来查看一下Y的字节码:

class org.levin.insidejvm.miscs.bridgemethod.Y extends org.levin.insidejvm.miscs.bridgemethod.X {

  public org.levin.insidejvm.miscs.bridgemethod.F getE();

    0 new org.levin.insidejvm.miscs.bridgemethod.F [16]

    3 dup

    4 invokespecial org.levin.insidejvm.miscs.bridgemethod.F() [18]

    7 areturn

 public bridge synthetic org.levin.insidejvm.miscs.bridgemethod.E getE();

    0 aload_0 [this]

    1 invokevirtual org.levin.insidejvm.miscs.bridgemethod.Y.getE() : org.levin.insidejvm.miscs.bridgemethod.F [20]

    4 areturn

}

从字节码上,我们可以看出语法本身事实上并没有发生变化,变化的只是编译器做的支持,它为重载方法重新生成了一个返回E而不是Fbridge方法。

从调用的字节码上可以更加明显的看出语法没有发生变化这一点:

     public   static   void  main(String[] args) {
       X x 
=   new  Y();
       x.getE();
    }

 

字节码如下:

 public static void main(java.lang.String[] args);

     0 new org.levin.insidejvm.miscs.bridgemethod.Y [16]

     3 dup

     4 invokespecial org.levin.insidejvm.miscs.bridgemethod.Y() [18]

    7 astore_1 [x]

     8 aload_1 [x]

     9 invokevirtual org.levin.insidejvm.miscs.bridgemethod.X.getE() : org.levin.insidejvm.miscs.bridgemethod.E [19]

    12 pop

13 return

该字节码中x.getE()方法事实上调用的就是生成的bridge方法(E getE())方法,而不是用户定义的F getE()方法。

这种重载机制在某些,不同子类某个函数的返回值是不一样的,但是他们都需要重写父类中方法,以可以在某个点上通过父类实例统一调用。只是这种机制就需要返回值必须是继承于同一个类。事实上,这种方式在没有引入这种重写机制的时候也是可以实现的,只是现在Java在编译器层面上提供了支持。

                                                                                                                    于2010年10月3日
注:这些文章都是前些时候写的,之前博客很乱,也都是随便贴一些自己写的或转载的,还有一些则是没有贴出来过的。现在打算好好整理一下,完整的记录自己的一些学习历程,而每次看到过去的时间,则让我想起以前的日子,因而我对时间一直是很重视的,所以每篇都著名写的日期,直到最先的文章出现。:)

目录
打赏
0
0
0
0
30
分享
相关文章
在Java中实现分布式事务的常用框架和方法
总之,选择合适的分布式事务框架和方法需要综合考虑业务需求、性能、复杂度等因素。不同的框架和方法都有其特点和适用场景,需要根据具体情况进行评估和选择。同时,随着技术的不断发展,分布式事务的解决方案也在不断更新和完善,以更好地满足业务的需求。你还可以进一步深入研究和了解这些框架和方法,以便在实际应用中更好地实现分布式事务管理。
|
5月前
|
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
141 9
|
28天前
|
《从头开始学java,一天一个知识点》之:方法定义与参数传递机制
**你是否也经历过这些崩溃瞬间?** - 看了三天教程,连`i++`和`++i`的区别都说不清 - 面试时被追问&quot;`a==b`和`equals()`的区别&quot;,大脑突然空白 - 写出的代码总是莫名报NPE,却不知道问题出在哪个运算符 🚀 这个系列就是为你打造的Java「速效救心丸」!我们承诺:每天1分钟,地铁通勤、午休间隙即可完成学习;直击痛点,只讲高频考点和实际开发中的「坑位」;拒绝臃肿,没有冗长概念堆砌,每篇都有可运行的代码标本。上篇:《输入与输出:Scanner与System类》 | 下篇剧透:《方法重载与可变参数》。
50 25
Java快速入门之数组、方法
### Java快速入门之数组与方法简介 #### 一、数组 数组是一种容器,用于存储同种数据类型的多个值。定义数组时需指定数据类型,如`int[]`只能存储整数。数组的初始化分为静态和动态两种: - **静态初始化**:直接指定元素,系统自动计算长度,如`int[] arr = {1, 2, 3};` - **动态初始化**:手动指定长度,系统给定默认值,如`int[] arr = new int[3];` 数组访问通过索引完成,索引从0开始,最大索引为`数组.length - 1`。遍历数组常用`for`循环。常见操作包括求和、找最值、统计特定条件元素等。
|
22天前
|
重学Java基础篇—Java Object类常用方法深度解析
Java中,Object类作为所有类的超类,提供了多个核心方法以支持对象的基本行为。其中,`toString()`用于对象的字符串表示,重写时应包含关键信息;`equals()`与`hashCode()`需成对重写,确保对象等价判断的一致性;`getClass()`用于运行时类型识别;`clone()`实现对象复制,需区分浅拷贝与深拷贝;`wait()/notify()`支持线程协作。此外,`finalize()`已过时,建议使用更安全的资源管理方式。合理运用这些方法,并遵循最佳实践,可提升代码质量与健壮性。
29 1
Java容器及其常用方法汇总
Java Collections框架提供了丰富的接口和实现类,用于管理和操作集合数据。
Java容器及其常用方法汇总
Java中的异常处理方法
本文深入剖析Java异常处理机制,介绍可检查异常、运行时异常和错误的区别与处理方式。通过最佳实践方法,如使用合适的异常类型、声明精确异常、try-with-resources语句块、记录异常信息等,帮助开发者提高代码的可靠性、可读性和可维护性。良好的异常处理能保证程序稳定运行,避免资源泄漏和潜在问题。
|
1月前
|
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
67 5
|
2月前
|
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
226 3
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
|
2月前
|
java.time常用方法汇总
`java.time` API 是从 Java 8 开始引入的时间日期处理库,旨在替代老旧的 `java.util.Date` 和 `Calendar`。它提供了更简洁、强大和灵活的方式处理日期、时间、时区及时间间隔,支持全球化和时间计算需求。API 包含获取当前时间、创建指定时间、解析和格式化字符串、进行加减运算、比较时间、获取年月日时分秒、计算时间间隔、时区转换以及判断闰年等功能。示例代码展示了如何使用这些功能,极大简化了开发中的时间处理任务。
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等