• 原文地址:Java bridge methods explained
  • 原文作者:STAS
  • 译文出自:掘金翻译计划
  • 本文永久链接:github.com/xitu/gold-m…
  • 译者:kezhenxu94
  • 校对者:

Java 中的桥接方法是一种合成方法,在实现某些 Java 语言特性的时候是很有必要的。最为人熟知的例子就是协变返回值类型和泛型擦除后导致基类方法的参数与实际调用的方法参数类型不一致。

看一下以下的例子:

public class SampleOne {public static class A<T> {public T getT() {return null;}}public static class  B extends A<String> {public String getT() {return null;}}
}
复制代码

事实上这就是一个协变返回类型的例子,泛型擦除后将会变成类似于下面这样的代码段:

public class SampleOne {public static class A {public Object getT() {return null;}}public static class  B extends A {public String getT() {return null;}}
}
复制代码

在将编译后的字节码反编译后,类 B 会是这样子的:

public class SampleOne$B extends SampleOne$A {
public SampleOne$B();
...
public java.lang.String getT();
Code:
0:   aconst_null
1:   areturn
public java.lang.Object getT();
Code:
0:   aload_0
1:   invokevirtual   #2; // 调用 getT:()Ljava/lang/String;
4:   areturn
}
复制代码

从上面可以看到,有一个新合成的方法 java.lang.Object getT(), 这在源代码中是没有出现过的。这个方法就起了一个桥接的作用,它所做的就是把对自身的调用委托给方法 jva.lang.String getT()。编译器不得不这么做,因为在 JVM 方法中,返回类型也是方法签名的一部分,而桥接方法的创建就正好是实现协变返回值类型的方式。

现在再看一看下面和泛型相关的例子:

public class SampleTwo {public static class A<T> {public T getT(T args) {return args;}}public static class B extends A<String> {public String getT(String args) {return args;}}
}
复制代码

编译后类 B 会变成下面这样子:

public class SampleThree$B extends SampleThree$A{
public SampleThree$B();
...
public java.lang.String getT(java.lang.String);
Code:
0:   aload_1
1:   areturnpublic java.lang.Object getT(java.lang.Object);
Code:
0:   aload_0
1:   aload_1
2:   checkcast       #2; //class java/lang/String
5:   invokevirtual   #3; //Method getT:(Ljava/lang/String;)Ljava/lang/String;
8:   areturn
}
复制代码

这里的桥接方法覆盖了(override)基类 A 的方法,不仅使用字符串参数将对自身的调用委派给基类 A 的方法,同时也执行了一个到 java.lang.String 的类型转换检测(#2)。这就意味着如果你运行下面这样的代码,忽略编译器的“未检”(unchecked)警告,结果会是从桥接方法那里抛出异常 ClassCastException

A a = new B();
a.getT(new Object()));
复制代码

以上例子就是桥接方法最为人熟知的两种使用场景,但至少还有一种使用案例,就是桥接方法被用于“改变”基类可见性。考虑以下示例代码,猜测一下编译器是否需要创建一个桥接方法:

package samplefour;public class SampleFour {static class A {public void foo() {}}public static class C extends A {}public static class D extends A {public void foo() {}}
}
复制代码

如果你反编译 C 类,你将会看到有 foo 方法,它覆盖了基类的方法并把对自身的调用委托给它(基类的方法):

public class SampleFour$C extends SampleFour$A{
...
public void foo();
Code:
0:   aload_0
1:   invokespecial   #2; //Method SampleFour$A.foo:()V
4:   return}
复制代码

编译器需要这样的方法,因为 A 类不是公开的,在 A 类所在包之外是不可见的,但是 C 类是公开的,它所继承来的所有方法在所在包之外也都应该是可见的。需要注意的是,D 类不会有桥接方法生成,因为它覆盖了 foo 方法,因此没有必要“提升”其可见性。 这种桥接方法似乎是由于这个 bug (在 Java 6 被修复)才引入的。这意味着在 Java 6 之前是不会生成这样桥接方法的,那么 C#foo 就不能够在它所在包之外使用反射调用,以致于下面这样的代码在 Java 版本小于 1.6 时会报 IllegalAccessException 异常。

package samplefive;
...
SampleFour.C.class.getMethod("foo").invoke(new SampleFour.C());
...
复制代码

不使用反射机制,正常调用的话是起作用的。

可能还有其他使用桥接方法的案例,但没有相关的信息来源。此外,关于桥接方法也没有明确的定义,尽管你可以很容易的猜测出来,像以上的示例是相当明显的,但如果有一些规范把桥接方法说明清楚的话就更好了。尽管自 Java 5 开始 Method#isBridge() 方法 就是公开的反射 API 了,桥接的标志也是字节码文件格式中的一部分,但 Java 虚拟机和 Java 语言规范都始终没有任何关于桥接方法的确切文档,也没有提供关于编译器何时/如何使用桥接方法的任何规则。我所能找到的全部就是在这里的“讨论区”的引用。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。

[译] Java 桥接方法详解相关推荐

  1. java函数方法详解(简单易懂)

    方法(函数) 函数的组成是: 访问修饰符 返回值 函数名(形式参数) {函数内容; } 更多java函数方法详解视频课程学习地址:https://ke.qq.com/course/149432  有技 ...

  2. java equals方法详解

    序言:准备总结一些java基础的知识方便以后查阅,从equals入手 目录: 等于(==)详解 equals方法详解 一.等于(==)详解 先明确一点:"==" 其实是存储地址的比 ...

  3. java clone() 方法详解及深克隆与浅克隆

    概述 clone 翻译过来就是 克隆,顾名思义就是创造一个一模一样的事物.Java 代码中 clone() 方法是 Object 方法,而 Object 又是所有类的父类,也就是说所有 java 对象 ...

  4. JAVA toString方法详解

    JAVA toString方法 在Java中,我们经常会编写许多自定义类.在使用时,我们如何打印出这些类中实例变量? class value {private int s;public void se ...

  5. JAVA本地方法详解,什么是JAVA本地方法?

    https://blog.csdn.net/wi__wi/article/details/51085907 前言: JAVA中有两种方法:JAVA方法和本地方法 JAVA方法是由JAVA编写的,编译成 ...

  6. JAVA Calendar方法详解

    究竟什么是一个 Calendar 呢?中文的翻译就是日历,那我们立刻可以想到我们生活中有阳(公)历.阴(农)历之分.它们的区别在哪呢? 比如有:     月份的定义 - 阳`(公)历 一年12 个月, ...

  7. Java main 方法详解

    1.main方法说起 编译完我们的java文件后,需要有个一含有main方法的类,java 命令将指示操作系统启动一个jvm进程 这个jvm进程启动后,寻找那个main地方开始执行程序 java [J ...

  8. Java回调方法详解

    回调在维基百科中定义为: 在计算机程序设计中,回调函数,是指通过函数参数传递到其他代码的,某一块可执行代码的引用. 其目的是允许底层代码调用在高层定义的子程序. 举个例子可能更明白一些:以Androi ...

  9. Java equals 方法详解

    equals()方法: 是一个方法,而非运算符 只能适用于引用数据类型 Object类中equls()的定义: public boolean equals(Object obj) {return (t ...

最新文章

  1. HDLC和 PPP的实验
  2. 三维列表转换成数组时,维度却只有二维
  3. mysql inner join
  4. Struts2源码阅读(三)_DispatcherConfigurationProvider
  5. JavaScript的类型自动转换高级玩法JSFuck
  6. 试题17 方程的解(枚举法)
  7. 深度学习花书-2.9 伪逆矩阵
  8. DirectAdmin面板在线解压缩的.tar.gz文件
  9. Linux 设置时区 命令
  10. ASP.NET2.0 GridView小技巧汇粹 (转)
  11. python 多线程 线程池的四种实现方式
  12. 律师视角下网络爬虫技术的罪与罚
  13. python f检验 代码_python f检验
  14. 使用PDman进行数据库设计
  15. 服务器做虚拟网吧,一种基于游戏的虚拟网吧实现方法
  16. 路德维希贝多芬计算机怎么操作,路德维希·凡·贝多芬(Ludwig van Beethoven)-欢乐颂(Ode an die Freude)...
  17. 怎样在小方框上打对号 小方框内打对勾 word 方框打对勾
  18. 又涨了?2023全国程序员薪资最新统计(文末附招聘岗位)
  19. 计算机开机跳过硬盘检查,怎么跳过开机硬盘自检的三个方法
  20. python查找中间值

热门文章

  1. 虚拟机概论(六)——JAVA虚拟机模型 (转载)
  2. Java实现简单的倒排索引
  3. 对于小白,创建一个网站你需要做什么?
  4. 滴滴收购优步谈判过程_大流行之后,优步正在为绿色业务做准备
  5. SDL解析——SDL简介
  6. 经济学经典著作选读书单
  7. JS逆向——裁判文书网(详细图文步骤)
  8. SAP报错:交易码 MWS 会计科目表 XXX 没有在表 T030K 中定义
  9. 魂系列联机服务器,黑暗之魂3 联机图文教程 新手联机指南 正版怎么联机_3DM单机...
  10. 三皇时期的远古帝王---地皇