1.什么是桥接方法

桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法。

判断方法

我们可以通过 Method.isBridge() 来判断一个方法是不是桥接方法。

桥接方法的 access_flag

在字节码中,桥接方法会被标记 ACC_BRIDGE 和 ACC_SYNTHETIC

  • ACC_BRIDGE 用来说明 桥接方法是由 Java 编译器 生成的
  • ACC_SYNCTHETIC 用来表示 该类成员没有出现在源代码中,而是由编译器生成

2.什么情况下会产生桥接方法(探究)

情况1:实现一个泛型接口

Consumer<T> 是 JDK 1.8 出现的接口方法:

public interface Consumer<T> {
void accept(T t);
}

我们来实现该接口

public class StringConsumer implements Consumer<String> {
@Override
public void accept(String s) {
System.out.println("i consumed " + s);
}
}

编译源代码后,查看字节码。

  • 使用 IDEA: View -> Show Bytecode

我们发现:

StringConsumer 类的字节码中包含 桥接方法



StringConsumer 类的字节码中还包含我们覆写后的 public void accept(String s) 方法

类似地,还有 JDK 1.8 出现的 Supplier<T>Function<T,R>, 接口实现类

public class StringSupplier implements Supplier<String> {

    @Override
public String get() {
return "null";
}
}

桥接方法如图所示:

相当于字节码中有一个源代码中所不存在的,由编译器产生的public Object get() 方法。

public class StringFunction implements Function<String, String> {

    @Override
public String apply(String s) {
return s + " , hello";
}
}

桥接方法如图所示:

相当于字节码中有一个源代码中所不存在的,由编译器产生的public Object apply(Object p)方法。

情况2:覆盖超类的方法,并升级返回类型

  • 修改参数类型(×)

    修改参数数量,类型,参数顺序,都会产生一个新的方法。
// 超类
public class BaseConsumer {
public void accept(Object object) {}
} // 子类
public class IntegerConsumer extends BaseConsumer {
public void accept(Object integer) {
System.out.println("consumed " + integer);
}
}
  • 修改返回类型(√)

    查看 IntegerSupplier 字节码可以找到桥接方法 public Object get()

// 超类
public class BaseSupplier {
public Object get() {
return "base";
}
}
// 子类
public class IntegerSupplier extends BaseSupplier {
public Integer get() {
return 110;
}
}
  • 保持原样(×)

    我们知道修改了参数不会产生桥接方法,但是我们保持方法的返回类型,方法名,参数,它也还是不会产生桥接方法
// 超类
public class BaseFunction {
public Object apply(Object src) {
return src;
}
} // 子类
public class IntegerFunction extends BaseFunction {
public Object apply(Object src) {
return src;
}
}

情况3:升级修饰符不会生成桥接方法

  • 访问限定符升级(×)
// 超类
public class BasePrinter {
protected void print() {
}
} // 子类
public class PublicPrinter extends BasePrinter {
public void print() {
System.out.println("print");
}
}

情况4:静态方法不会生成桥接方法

  • 子类的静态方法属于子类,父类的静态方法属于父类。
  • 静态方法在继承中不存在覆盖。也不会生成桥接方法
// 超类
public class SuperClass {
public static void staticMethod() {
System.out.println("SuperClass");
}
}
// 子类
public class SubClass extends SuperClass {
public static void staticMethod() {
System.out.println("SubClass");
}
}

3. 为什么要生成桥接方法

我们再来看这个方法

public class StringFunction implements Function<String, String> {

    @Override
public String apply(String s) {
return s + " , hello";
} public static void main(String[] args) {
Function func = new StringFunction();
System.out.println(func.apply(new Object()));
}
}

运行main函数,会产生以下异常。



发生错误的代码是func.apply(new Object()),这里会调用桥接方法。



查看字节码,我们发现,桥接方法进行运行时类型检查时,会抛出异常,但是真正发生作用的还是 StringFunction 类 String apply(String s)

我们重新聚焦为什么要生成桥接方法

在java1.5以前,比如声明一个集合类型:

List list = new ArrayList();

那么往list中可以添加任何类型的对象,但是在从集合中获取对象时,无法确定获取到的对象是什么具体的类型,所以在1.5的时候引入了泛型,在声明集合的时候就指定集合中存放的是什么类型的对象:

List<String> list = new ArrayList<String>();

使用泛型之后,在获取时就不必担心类型的问题,因为泛型在编译时编译器会检查往集合中添加的对象的类型是否匹配泛型类型,如果不正确会在编译时就会发现错误,而不必等到运行时才发现错误。

因为泛型是在1.5引入的,为了向前兼容,所以会在编译时去掉泛型(泛型擦除),产生桥接方法。

总结

桥接方法的产生:

  • 实现 泛型接口 的类,编译器会为 这个类生成 桥接方法
  • 继承 超类的 方法,并且升级方法的返回类型(即子类 覆写 超类方法 时,返回类型 升级为 原返回类型的父类)

为什么需要生成桥接方法?

  • 因为泛型是在1.5引入的,为了向前兼容,所以会在编译时去掉泛型(泛型擦除),产生桥接方法

参考文献

java中什么是bridge method(桥接方法)

桥接方法的 access_flag

Java Language Specification

Java基础之Bridge method(桥接方法)的更多相关文章

  1. Java基础关于Map(字典)的方法使用

    Java基础关于Map(字典)的方法使用 java中一般用map与hashmap来创建一个key-value对象 使用前提是要导入方法包: import java.util.HashMap: impo ...

  2. java反射的补充:桥接方法以及Spring中一些工具类

    在上一篇博文中:http://www.cnblogs.com/guangshan/p/4660564.html 源码中有些地方用到了 this.bridgedMethod = BridgeMethod ...

  3. 【java基础 13】两种方法判断hashmap中是否形成环形链表

    导读:额,我介绍的这两种方法,有点蠢啊,小打小闹的那种,后来我查了查资料,别人都起了好高大上的名字,不过,本篇博客,我还是用何下下的风格来写.两种方法,一种是丢手绢法,另外一种,是迷路法. 这两种方法 ...

  4. Java基础系列--03_Java中的方法描述

    方法 (1)方法的定义:就是完成特定功能的代码块. 注意:在很多语言里面有函数的定义,而在Java中,函数被称为方法. (2)格式: 修饰符 返回值类型 方法名(参数类型 参数名1,参数类型 参数名2 ...

  5. 【java基础】为什么重写toString()方法?

    不得不说,有很多java初学者写java实体类的时候,并没有真正理解重写toString() 方法,可能是口头知道也可能是跟风随带添加toString() 方法,并没有真正理解其意义,如果真要被问起来 ...

  6. Java基础系列(28)- 方法的定义和调用

    方法的定义 Java的方法类似于其他语言的函数,是一段用来完成特定功能的代码片段,一般情况下,定义一个方法包含以下语法: 修饰符 返回值类型 方法名(参数类型 参数名){ -- 方法体 -- retu ...

  7. Java基础知识点2:hashCode()方法

    hashCode()方法基本实现 hashCode方法是Java的Object类所定义的几个基本方法之一.我们可以深入到Object类的源码中去查看: public native int hashCo ...

  8. Java基础集锦——利用Collections.sort方法对list排序

    要想对List进行排序,可以让实体对象实现Comparable接口,重写compareTo方法即可实现按某一属性排序,但是这种写法很单一,只能按照固定的一个属性排序,没变法变化.通过下面这种方法,可以 ...

  9. Java基础:HashMap中putAll方法的疑惑

    最近回顾了下HashMap的源码(JDK1.7),当读到putAll方法时,发现了之前写的TODO标记,当时由于时间匆忙没来得及深究,现在回顾到了就再仔细思考了下 @Override public v ...

随机推荐

  1. 6、struct2使用servlet的api函数

    方法一: Struts2的Action访问Servlet API 可以通过实现装配接口没,完成对Servlet API的访问 * ServletRequestAware取得HttpServletReq ...

  2. 04[掌握] Java连接redis操作

    1,Jedis所需要的jar包依赖 <dependency> <groupId>redis.clients</groupId> <artifactId> ...

  3. python计算图像信息熵

    import cv2 import numpy as np import math import time def get_entropy(img_): x, y = img_.shape[0:2] ...

  4. vue全家桶(2.7)

    3.11.1.vue-router中的全局钩子函数 在vue-router中,路由发生变化,我们可以做一些事情,例如:可以决定是否进入导航,可以决定跳转到哪里,官方文档中又叫做导航守卫 首先来看一个全 ...

  5. [USACO11JAN]Roads and Planes G【缩点+Dij+拓补排序】

    题目 Farmer John正在一个新的销售区域对他的牛奶销售方案进行调查.他想把牛奶送到T个城镇 (1 <= T <= 25,000),编号为1T.这些城镇之间通过R条道路 (1 < ...

  6. 告别传统机房:3D 机房数据可视化实现智能化与VR技术的新碰撞

    前言 随着各行业对计算机依赖性的日益提高,计算机信息系统的发展使得作为其网络设备.主机服务器.数据存储设备.网络安全设备等核心设备存放地的计算机机房日益显现出它的重要地位,而机房的环境和动力设备如供配 ...

  7. spark | 手把手教你用spark进行数据预处理

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是spark专题的第七篇文章,我们一起看看spark的数据分析和处理. 过滤去重 在机器学习和数据分析当中,对于数据的了解和熟悉都是最基 ...

  8. JAVA基础-继承机制

    需要掌握的知识点: 包的使用 继承时,子类如何覆盖父类方法(重写和重载) 继承时,构造方法的执行过程 JVM中子类如何实例化(先实例化父,再实例化子) super和this关键字 final关键字 包 ...

  9. 大多数人可能都不会使用socketTimeout,看了底层才知道一直都做错了

    前几天一个机房网络抖动,引发了很多对外请求的超时问题,在发生问题排查日志的时候,发现了这么一个现象,httpclient我们的请求超时时间并没有按照我们的设置报超时异常 我们的大概配置如下: Requ ...

  10. 三色二叉树——树形dp

    三色二叉树 题目描述 一棵二叉树可以按照如下规则表示成一个由 \(0.1.2\) 组成的字符序列,我们称之为"二叉树序列 \(S\) ": \(0\) 该树没有子节点. \(1S_ ...