范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

Java底层知识什么是桥接方法?

  导语
  笔者在最近的日常工作中,因业务需要,研究 Java 字节码层面的知识。具体是,需要根据类字节码,获取特定方法名的方法入参,此方法名在源码中只有一个。但是在实际使用中发现:在类实现泛型接口的情况下,在字节码层面,类却有两个同名方法,导致无法确定哪个方法才是我们需要的方法。经过研究发现,其中一个方法是编译器在编译的过程中,自动生成的 桥接方法(bridge method) ,两个方法可通过特定标识区分。
  注:此处的桥接方法,跟设计模式中的桥接模式,不是一个概念。
  问题描述
  为了能够说明问题,笔者模糊了实际业务场景的具体案例,用一个稍微简单,能够说明问题的示例,来分析编译器自动生成的桥接方法(bridge method)。
  我们知道, Java 泛型 是JDK 5 中引入的一个新特性,应用广泛。比如,我们有一个操作算子泛型接口 Operator,接口中有一个 process(T t) 方法,其作用是对入参 T 进行逻辑处理。示例代码如下:
  /**
  * @author renzhiqiang
  * @date 2022/2/20 18:30
  */
  public interface Operator {
  /**
  * process method
  * @param t
  */
  void process(T t);
  }
  在实际业务场景中,我们会有不同的操作算子,实现Operator 接口,进行业务逻辑处理。那么我们来创建一个具体的算子,并实现Operator 接口,重写 process(T t) 方法。如下:
  /**
  * 用户信息算子
  * @author renzhiqiang
  * @date 2022/2/20 18:30
  */
  public class UserInfoOperator implements Operator {
  @Override
  public void process(String s) {
  // do something
  }
  }
  其中,泛型接口中的入参类型 T,在实现类中替换成了实际需要的类型 java.lang.String。到这里,我们就准备好了代码样例。
  那么,我们的目标是什么呢?就是要获取UserInfoOperator#process(String s) 方法的参数类型java.lang.String。读到这里,读者可能会想:这不很简单么,通过反射,根据Class#getDeclaredMethods(),获取到 UserInfoOperator 的所有方法,再找到方法名是 process 的方法,然后再获取到参数列表,不就可以获取参数类型java.lang.String 了么。
  如果正在阅读文章的你也这么想的话,那请继续往下看。
  根据 Java 反射方法Class#getDeclaredMethods ()  的描述:
  Returns an array of Method objectsincluding public, protected, default (package) access, and private methods, butexcludes inherited methods.
  翻译过来就是: 返回方法对象数组,包括公共方法、受保护方法、默认(包)访问方法和私有方法,但不包括继承方法。
  根据我们的示例,如果我们通过反射,利用Class#getDeclaredMethods() 方法,我们预期的返回方法数组中,应该只有一个方法名是process 才对,但是这里却有两个 process 方法。惊不惊奇,意不意外!
  图 debug 发现 UserInfoOperator 类的两个 process 方法
  产生原因
  编译器生成 bridge 方法
  我们知道,Java 源码需要经过编译器编译,生成对应的 .class 文件,才能给 JVM 使用。在源码中,我们只定义了一个名为 process 的方法。那么我们考虑,编译器在编译源码的过程中,是否会进行一些特的处理。为了更加直观地查看编译后的字节码文件,在 Idea 安装 jclasslib 插件,通过 jclasslib 查看 UserInfoOperator 和 Operator 的字节码。如下:
  图 jclasslib 查看 UserInfoOperator 类的字节码(第一个 process 方法)
  图 jclasslib 查看 UserInfoOperator 类的字节码 (第二个 process 方法)
  图 jclasslib 查看 Operator 类的字节码
  通过 jclasslib 查看 .class 文件发现,在 UserInfoOperator 类中确实存在两个 process 方法:其中一个方法的入参是 java.lang.String,另一个方法的入参是 java.lang.Object。而在 Operator 字节码中,只有一个 process 方法,方法的入参是 java.lang.Object。同时我们注意到,在 UserInfoOperator 类的字节码中, [访问标志]项,其中一个方法的访问标志是 [public synthetic bridge]。其中 public 很好理解,但是其中的 [synthetic bridge] 是怎么来的呢?
  查阅相关资料后发现,标识符 synthetic ,表示此方法是否是由编译器自动产生的;标识符 bridge,表示此方法是否是由编译器产生的桥接方法。
  图 方法访问标志(来源:深入理解 Java 虚拟机(第三版))
  到此,可以确定的是,其中一个process 方法,是编译器自动产生的桥接方法。那么为什么编译器会产生桥接方法呢?以及在什么情况下,会产生桥接方法?以及如何判断一个方法是不是桥接方法?我们继续往下分析。
  为何生成 bridge 方法
  正确编译
  在源码中,Operator 类的 process 方法的参数定义是 process(T t),参数类型是 T。而在字节码层面我们看到,process 方法在编译之后,编译器将入参类型变成了 java.lang.Object。伪代码示意,大概是这样:
  public interface Operator {
  /**
  * 方法参数变成 Object 类型
  * @param object
  */
  void process(Object object);
  }
  想象一下,如果没有编译器自动生成的桥接方法,那么在编译层面是不能通过的:因为接口 Operator 中的 process 方法,,经过编译之后,参数类型变成了 java.lang.Object 类型,而实现类 UserInfoOperator 中的 process 方法的参数是 java.lang.String 类型,两者的方法参数不一致,导致UserInfoOperator 并没有重写接口中的 process 方法,因此编译无法通过。
  这种情况下,编译器自动生成一个桥接方法 void process(Object obj) 方法,则可以编译通过,似乎是理所当然的事情。自动生成的 process方法,方法签名为:void process(Object object)。伪代码示意,大概是这样:
  // 自动生成的process 方法
  public void process(Object object) {
  process((String) object);
  }
  类型擦除
  我们知道,Java 中的泛型在编译期间会将泛型信息擦除。如代码定义 List 和 List,编译之后都会变成 List。我们再考虑一种常见的情形:Java 类库中比较器的用法。我们自定义比较器的时候,可以通过实现 Comparator 接口,实现比较逻辑。示例代码如下:
  public class MyComparator implements Comparator {
  public int compare(Integer a,Integer b) {
  // 比较逻辑
  }
  }
  这种情况下,编译器同样会产生一个桥接方法。方法签名为 intcompare(Object a, Object b) 。
  图 MyComparator 类的两个 compare 方法
  伪代码示意,大概是这样:
  public class MyComparator implements Comparator {
  public int compare(Integer a,Integer b) {
  // 比较逻辑
  }
  // 桥接方法 (bridge method)
  public int compare(Object a,Object b) {
  return compare((Integer)a,(Integer)b);
  }
  }
  因此,当我们使用如下方式进行比较的时候,能够通过编译并得到我们预期的结果:
  Object a = 5;
  Object b = 6;
  Comparator rawComp = new MyComparator();
  // 可以通过编译,因为自动生成了桥接方法compare(Object a, Object b)
  int comp = rawComp.compare(a, b);
  另外,我们知道,泛型编译之后,类型信息会被擦除。如果我们有这样一个比较方法:
  // 比较方法
  public  T max(List list, Comparator comparator){
  T biggestSoFar = list.get(0);
  for ( T t : list ) {
  if (comparator.compare(t,biggestSoFar) > 0) {
  biggestSoFar = t;
  }
  }
  return biggestSoFar;
  }
  编译之后,泛型被擦除掉,伪代码表示,大概是这样:
  public Object max(List list, Comparator comparator) {
  Object biggestSoFar =list.get(0);
  for ( Object t : list ) {
  if (comparator.compare(t,biggestSoFar) > 0) { //比较逻辑
  biggestSoFar = t;
  }
  }
  return biggestSoFar;
  }
  我们将 MyComparator 其中一个参数传入 max() 方法。如果没有桥接方法的话,那么第四行的比较逻辑,将无法正确编译,因为MyComparator 类中没有两个参数是 Object 类型的比较方法,只有参数类型是 Integer 类型的比较方法。读者可自行测试。
  解决方案
  通过以上的案例描述,我们知道,在实现泛型接口的场景下,编译器会自动生成桥接方法,保证编译能够通过。那么在这种情况下,我们只要识别哪一个是桥接方法,哪一个不是桥接方法,就可以解决我们一开始的问题。很自然的,既然编译器自动产生了一个桥接方法,那么应该会有某种方式,可以让我们判断一个方法是否是桥接方法。
  果然,我们继续研究发现,Method 类中提供了 Method#isBridge() 方法。查看源码中对方法的描述:Method#isBridge():Returns true if this method is a bridge method;returns false otherwise。
  到此,我们通过反射,获取到 UserInfoOperator 类中的两个process 方法,再调用 Method#isBridge() 方法,即可锁定需要的方法,因而进一步获取方法参数 java.lang.String。
  深入分析
  至此可以说,就业务需求来说,我们完美的找到了解决方案。但在此之后,不禁会想:除了上述示例,还有哪些情况下,编译器也会自动生成桥接方法呢?我们继续深入研究。
  类继承
  通过查阅相关资料,我们考虑如下一种情况:
  /**
  * 如下会产生桥接方法吗?
  * @author renzhiqiang
  * @date 2022/2/20 18:33
  */
  public class BridgeMethodSample {
  static class A {
  public void foo() {
  }
  }
  public static class C extends A{
  }
  public static class D extends A{
  @Override
  public void foo() {
  }
  }
  }
  上述代码示例中,我们定义了三个静态内部类:A C D,其中 C D 分别继承 A。经过编译,通过jclasslib 查看 BridgeMethodSample 字节码,我们也发现:类 C 中编译器为其生成了桥接方法 void foo(),而类 D 中却没有。
  图 类C 生成桥接方法
  图 类D 没有生成桥接方法
  深入分析,并根据上述分析的经验,我们猜测,编译器生成桥接方法,一定是在某种情况下需要一个方法,来满足 Java 编程规范,或者需要保证程序运行的正确性。通过字节码可以看出,类 A 没有 public 修饰,包范围以外的程序是没有访问类 A 的权限的,更不用说类 A 中的方法。
  但是类 C 是有public 修饰,C 类中的方法,包括继承来的方法,是可以被包外的程序访问的。因此,编译器需要生成一个桥接方法,以保证能够访问 foo() 方法,满足程序的正确运行。但是,类 D 同样继承 A,却没有生成桥接方法,根本原因是类 D 中重写了父类 A 中的 foo() 方法,即没有必要生成桥接方法。
  方法重写
  我们再看一种情况,方法重写。
  Java 中,方法重写(Override),是子类对父类的允许访问的方法的实现过程进行重新编写的过程。重写需要满足一定的规则:
  1. The method must have the same name as in the parentclass.
  2. The method must have the same parameter as in theparent class.
  3. There must be an IS-A relationship (inheritance).
  JDK 5 之后,重写方法的返回类型,可以与父类方法返回类型相同,也可以不相同,但必须是父类方法返回类型的子类。我们考虑如下代码示例:
  // 定义一个父类,包含一个 test() 方法
  public class Father {
  public Object test(String s) {
  return s;
  }
  }
  // 定义一个子类,继承父类
  public class Child extends Father {
  @Override
  public String test(String s) {
  return s;
  }
  }
  以上,在 Child 子类中,我们重写了 test() 方法,但是返回值的类型,我们将 java.lang.Object 改变为它的子类 java.lang.String。编译之后,我们同样使用 jclasslib 插件,查看两个类的字节码,如下所示:
  图 Child 类字节码test() 方法(1)
  图 Child 类字节码test() 方法(2)
  图 Father类字节码test() 方法
  根据上图我们发现,Child 类中我们重写了 test() 方法,但是在字节码层面,发现有两个 test() 方法,其中一个方法的访问标志为 [public synthetic bridge], 表示这个方法是编译器为我们生成的。而当我们不改变 Child#test() 方法的返回类型时,编译器并没有为我们生成桥接方法,读者可自行试验。
  也就是说,在子类方法重写父类方法,返回类型不一致的情况下,编译器也为我们生成了桥接方法。
  以上,笔者罗列了几种编译器为我们自动生成桥接方法的情况。那么是否还有其他场景下,编译器也会生成桥接方法呢?如果您也曾研究过或者使用过 bridge 方法,欢迎交流讨论。
  同时,给出一个 bridge 方法的非官方定义,希望能够给读者一些启发:
  Bridge Method:  These are methods that create an intermediate layerbetween the source and the target functions. It is usually used as part of thetype erasure process. It means that the bridge method is required as a typesafe interface.
  限于笔者水平有限,难免有理解不准确、不到位的地方。欢迎交流讨论!
  参考
  https://stackoverflow.com/questions/5007357/java-generics-bridge-method
  https://stackoverflow.com/questions/14144888/find-generic-method-with-actual-types-from-getdeclaredmethods
  https://www.geeksforgeeks.org/method-class-isbridge-method-in-java/

惊喜福利到,抚州宝骏360降价3。34,期待您的光临今天给大家推荐一款车它就是宝骏360,特别适合日常使用,据悉五菱汽车抚州国鼎销售中心店即日起到10月31日限时回馈用户,最高优惠0。2万元,对宝骏360感兴趣的朋友不妨亲自体验一下AMD锐龙2600价格跟i39100F相当,你选谁?电脑装好后发现选错了Hello大家好,我是兼容机之家的咖啡。电脑硬件经过这么多年的发展的,技术已经越来越成熟,生产成本肯定是会有所降低,毕竟摩尔定律在那摆着。所以电脑也就会越来越加的便宜,但是也不排除AMD锐龙座驾B550主板曝光,支持PCIE4。0,价格方面是惊喜Hello大家好,我是兼容机之家的小牛!去年年中的时候,AMD正式发布了三代锐龙处理器,随同新处理器一同解禁的还有X570主板,定位于高端,此时中端主板B550(暂定)缺席。因为三五菱星辰新车SUV上市,更年轻更大气,价格有诚意迎来新车上市的黄金季,有心买车的朋友也开始蠢蠢欲动有所行动了。新车太多很多人都看得眼花缭乱,有没有哪款特别个性别致,不能错过的?还真有。五菱家族9月16日上市的五菱星辰各方面就十分五菱如此宠粉?马卡龙小车再上梅洛蓝生椰白新配色,价格不变从销量层面来看,现阶段国内卖得最好的是上汽通用五菱旗下的宏光MINIEV。因为自该车上市一年多以来,其销量已经突破40万大关,于今年18月还四度夺得全球新能源销量冠军。且比较有意思还在为电脑卡顿而烦恼吗?试试这个方法,硬盘速度快了十倍不止Hello大家好,我是兼容机之家的小牛!随着时代的发展,NAND闪存技术的不断的升级,闪存芯片的性能和产量都得到大幅度的提升,如今的固态的价格已经不是几年前能比的。虽然因为某些不可微软发布win10全新版本!网友建议隔离14天后再更新,以防BUG太多Hello大家好,我是兼容机之家的小牛!微软发布win10全新版本!网友建议隔离14天后再更新,以防BUG太多!自从Windows10系统发布以来,微软就一直想方设法的让Windo宁德时代再投320亿,押注磷酸铁锂布局电池材料回收电车汇消息10月12日,宁德时代发布公告称,拟由控股子公司广东邦普及其控股子公司在湖北省宜昌市姚家港化工园田家河片区投资建设邦普一体化电池材料产业园项目,项目投资总金额不超过320抚州宝骏RS5热销中,直降5。11要现车?要省钱?要省心?要优惠?要豪礼?今天它们都来了!五菱汽车抚州国鼎销售中心店宝骏RS5限时促销,即日起到10月29日,限时特惠0。5万元,欢迎您到店开启更多惊喜福利,店铺地址抚州五菱荣光小卡店内限时特惠0。155万元,欢迎到店鉴赏本周五菱荣光小卡最新报价五菱汽车抚州国鼎销售中心店10月29日10月29日限时促销,降价3。79,如此优惠的降幅,大家可千万不要错过,店铺地址抚州市文昌大道与迎宾大道交叉口促销时间抚州宝骏360购车优惠0。2万元,欢迎到店赏车心心念念的宝骏360,等了这么久终于有优惠了,小编打听到五菱汽车抚州国鼎销售中心店即日起到10月29日购车优惠3。34,这么好的机会朋友们是不是也和小编一样迫不及待的想到店去感受一
价格只有戴森的一半,这款带净化和负离子功能的无叶风扇很强大风扇可以说是最常见的消暑工具,因其成本低使用简单历经几十年依然大受欢迎。这么长时间以来,直流变频智能化是风扇发展的潮流,但产品形态一直没有大的突破。近年来,兴起的无叶风扇可以说是风发三个段子,绝对把你笑翻01hr风险对冲眼下又出了一个经济新名词风险对冲,那什么是风险对冲呢?一位老太太背包进银行存50万美金,总裁在VIP室亲自接待。总裁您老一生的积蓄?老太太哪里?我以豪赌为生,逢赌必照片倒过来看,牙都笑掉01生活换位思考,珍惜才配拥有!这张图告诉我们生活本来不容易,当你觉得容易的时候,肯定是有人在替你承担属于你的那份不容易,生活经常换位思考,珍惜才配拥有。02想法换位思考,感恩与理错峰装机,2021618装机配件选购指南前言一年一度的618就要到了,错过去年双11的消费者一定非常后悔成为等等党。饱受各类硬件涨价困扰的朋友们一定期待这次618能用惊爆的价格买到自己心仪产品,鉴于目前市场上某些硬件价格不要争争吵吵,生命其实一晃就老不要斤斤计较,时光属于你的越来越少不要争争吵吵,相处是缘,一晃就老不要过多抱怨,相遇本来就最美好不要互相拆台,说不定哪天就永远分开不要愁眉苦脸,生活本该微笑不要整天多爱多爱只需认真千万不要招惹老年人,不然哈哈哈哈01hr我不老,还是个壮小伙儿我不老,请叫我阿妹在公园里,总会看到年轻人仗着一身肌肉挑战大爷大妈的霸主地位。小伙咬牙切齿,面目狰狞,只做了3个大爷云淡风轻,毫无压力,一口气做了6个一机多能小狗T12ProRinse擦地吸尘器开箱作为创业者的我,最近刚为团队找到一个新的办公地点,由于预算有限选了一个地处偏远但是面积很大的场所,像是个studio,既用来办公,又用作团队成员的宿舍,扫地机作为标配自然没有缺这6个笑话实在是太逗了!让大伙都乐乐别人生的和自己生的老婆在家试穿一件新衣服,问老公漂不漂亮,老公回答说嗯,衣服挺漂亮的。老婆扭头又问儿子妈妈漂亮,还是衣服漂亮?儿子大声道妈妈漂亮!老婆撇撇嘴,感叹道别人生的和自己生发几个笑话,开心一整天01hr挑食路过幼儿园,偷听两个小朋友的谈话第一个小朋友说为什么挑食的都是孩子,家长怎么都不挑食呢?第二个小朋友说他们买的都是自己爱吃的,还挑什么食?我瞬间石化了,简直是真理啊!0爱子七不责,你知道几个?1对众不责在大庭广众之下,不要责备孩子,要在众人面前给孩子以尊严。2愧悔不责如果孩子已经为自己的过失感到惭愧后悔了,大人就不要责备孩子了。3暮夜不责晚上睡觉前不要责备孩子。此时责备路由器旁边放个它,信号立马满格,快来看一看那么今天小编给大家带来了个小妙招,只要在路由器上放上它,就会使你的无线信号立马满格哟,我们一起来看一看。之前大家对把易拉罐放在旁边这个方法并不陌生吧,但是没有见到一点的效果而且也是