【java编程】java中什么是bridge method(桥接方法)
https://blog.csdn.net/mhmyqn/article/details/47342577
https://www.cnblogs.com/strinkbug/p/5019453.html
在看spring-mvc的源码的时候,看到在解析handler方法时,有关于获取桥接方法代码,不明白什么是桥接方法,经过查找资料,终于理解了什么是桥接方法。
什么是桥接方法
桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法。
我们可以通过Method.isBridge()方法来判断一个方法是否是桥接方法,在字节码中桥接方法会被标记为ACC_BRIDGE和ACC_SYNTHETIC,其中ACC_BRIDGE用于说明这个方法是由编译生成的桥接方法,ACC_SYNTHETIC说明这个方法是由编译器生成,并且不会在源代码中出现。可以查看jvm规范中对这两个access_flag的解释http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.6。
有如下3个问题:
- 什么时候会生成桥接方法
- 为什么要生成桥接方法
- 如何通过桥接方法获取实际的方法
什么时候会生成桥接方法
那什么时候编译器会生成桥接方法呢?可以查看JLS中的描述http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.4.5。
就是说一个子类在继承(或实现)一个父类(或接口)的泛型方法时,在子类中明确指定了泛型类型,那么在编译时编译器会自动生成桥接方法(当然还有其他情况会生成桥接方法,这里只是列举了其中一种情况)。如下所示:
package com.mikan; /**
* @author Mikan
* @date 2015-08-05 16:22
*/ public interface SuperClass<T> { T method(T param); } package com.mikan; /**
* @author Mikan
* @date 2015-08-05 17:05
*/ public class SubClass implements SuperClass<String> { public String method(String param) { return param; } }
来看一下SubClass的字节码:
localhost:mikan mikan$ javap -c SubClass.class
Compiled from "SubClass.java"
public class com.mikan.SubClass implements com.mikan.SuperClass<java.lang.String> {
public com.mikan.SubClass();
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 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/mikan/SubClass;
public java.lang.String method(java.lang.String);
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: aload_1
1: areturn
LineNumberTable:
line 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 this Lcom/mikan/SubClass;
0 2 1 param Ljava/lang/String;
public java.lang.Object method(java.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 #2 // class java/lang/String
5: invokevirtual #3 // Method method:(Ljava/lang/String;)Ljava/lang/String;
8: areturn
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/mikan/SubClass;
0 9 1 x0 Ljava/lang/Object;
}
localhost:mikan mikan$
SubClass只声明了一个方法,而从字节码可以看到有三个方法,第一个是无参的构造方法(代码中虽然没有明确声明,但是编译器会自动生成),第二个是我们实现的接口中的方法,第三个就是编译器自动生成的桥接方法。可以看到flags包括了ACC_BRIDGE和ACC_SYNTHETIC,表示是编译器自动生成的方法,参数类型和返回值类型都是Object。再看这个方法的字节码,它把Object类型的参数强制转换成了String类型,再调用在SubClass类中声明的方法,转换过来其实就是:
public Object method(Object param) {
return this.method(((String) param));
}
也就是说,桥接方法实际是是调用了实际的泛型方法,来看看下面的测试代码:
package com.mikan; /**
* @author Mikan
* @date 2015-08-07 16:33
*/ public class BridgeMethodTest { public static void main(String[] args) throws Exception { SuperClass superClass = new SubClass(); System.out.println(superClass.method("abc123"));// 调用的是实际的方法 System.out.println(superClass.method(new Object()));// 调用的是桥接方法 } }
这里声明了SuperClass类型的变量指向SubClass类型的实例,典型的多态。在声明SuperClass类型的变量时,不指定泛型类型,那么在方法调用时就可以传任何类型的参数,因为SuperClass中的方法参数实际上是Object类型,而且编译器也不能发现错误。在运行时当参数类型不是SubClass声明的类型时,会抛出类型转换异常,因为这时调用的是桥接方法,而在桥接方法中会进行强制类型转换,所以才会抛出类型转换异常。上面的代码输出结果如下:
abc123 Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String at com.mikan.SubClass.method(SubClass.java:7) at com.mikan.BridgeMethodTest.main(BridgeMethodTest.java:27) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
如果我们在声明SuperClass类型的变量就指定了泛型类型:
SuperClass<String> superClass = new SubClass();
当然这里类型只能是String,因为SubClass的泛型类型声明是String类型的,如果指定其他类型,那么在编译时就会错误,这样就把类型检查从运行时提前到了编译时。这就是泛型的好处。
为什么要生成桥接方法
上面看到了编译器在什么时候会生成桥接方法,那为什么要生成桥接方法呢?
在java1.5以前,比如声明一个集合类型:
List list = new ArrayList();
那么往list中可以添加任何类型的对象,但是在从集合中获取对象时,无法确定获取到的对象是什么具体的类型,所以在1.5的时候引入了泛型,在声明集合的时候就指定集合中存放的是什么类型的对象:
List<String> list = new ArrayList<String>();
那么在获取时就不必担心类型的问题,因为泛型在编译时编译器会检查往集合中添加的对象的类型是否匹配泛型类型,如果不正确会在编译时就会发现错误,而不必等到运行时才发现错误。因为泛型是在1.5引入的,为了向前兼容,所以会在编译时去掉泛型(泛型擦除),但是我们还是可以通过反射API来获取泛型的信息,在编译时可以通过泛型来保证类型的正确性,而不必等到运行时才发现类型不正确。由于java泛型的擦除特性,如果不生成桥接方法,那么与1.5之前的字节码就不兼容了。如前面的SuperClass中的方法,实际在编译后的字节码如下:
localhost:mikan mikan$ javap -c -v SuperClass.class
Classfile /Users/mikan/Documents/workspace/project/algorithm/target/classes/com/mikan/SuperClass.class
Last modified 2015-8-7; size 251 bytes
MD5 checksum 2e2530041f1f83aaf416a2ca3af9b7e3
Compiled from "SuperClass.java"
public interface com.mikan.SuperClass<T extends java.lang.Object>
Signature: #7 // <T:Ljava/lang/Object;>Ljava/lang/Object;
SourceFile: "SuperClass.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
#1 = Class #10 // com/mikan/SuperClass
#2 = Class #11 // java/lang/Object
#3 = Utf8 method
#4 = Utf8 (Ljava/lang/Object;)Ljava/lang/Object;
#5 = Utf8 Signature
#6 = Utf8 (TT;)TT;
#7 = Utf8 <T:Ljava/lang/Object;>Ljava/lang/Object;
#8 = Utf8 SourceFile
#9 = Utf8 SuperClass.java
#10 = Utf8 com/mikan/SuperClass
#11 = Utf8 java/lang/Object
{
public abstract T method(T);
flags: ACC_PUBLIC, ACC_ABSTRACT
Signature: #6 // (TT;)TT;
}
localhost:mikan mikan$
通过Signature: #7 // <T:Ljava/lang/Object;>Ljava/lang/Object;可以看到,在编译完成后泛型实际上就成了Object了,所以方法实际上成了
public abstract Object method(Object param);
而SubClass实现了SuperClass这个接口,如果不生成桥接方法,那么SubClass就没有实现接口中声明的方法,语义就不正确了,所以编译器才会自动生成桥接方法,来保证兼容性。
如何通过桥接方法获取实际的方法
我们在通过反射进行方法调用时,如果获取到桥接方法对应的实际的方法呢?可以查看spring中org.springframework.core.BridgeMethodResolver类的源码。实际上是通过判断方法名、参数的个数以及泛型类型参数来获取的。
【java编程】java中什么是bridge method(桥接方法)的更多相关文章
- java中什么是bridge method(桥接方法)
java中什么是bridge method(桥接方法) https://blog.csdn.net/z69183787/article/details/81115524
- Java编程思想中关于闭包的一个例子
Java编程思想中的一个例子,不是很理解使用闭包的必要性,如果不使用闭包,是不是有些任务就不能完成?继续探索. package InnerClass; interface Incrementable ...
- JAVA编程思想中总结的与C++的区别
Java和C++都是面向对象语言.也就是说,它们都能够实现面向对象思想(封装,继乘,多态).而由于c++为了照顾大量的C语言使用者,而兼容了C,使得自身仅仅成为了带类的C语言,多多少少影响了其面向对象 ...
- java web 项目中获取当前路径的几种方法
1.jsp中取得路径: 以工程名为TEST为例: (1)得到包含工程名的当前页面全路径:request.getRequestURI() 结果:/TEST/test.jsp (2)得到工程名:req ...
- Java Web项目中连接Access数据库的配置方法
本文是对前几天的"JDBC连接Access数据库的几种方式"这篇的升级.因为在做一些小项目的时候遇到的问题,因此才决定写这篇博客的.昨天已经将博客公布了.可是后来经过一些验证有点问 ...
- java.nio.ByteBuffer中flip、rewind、clear方法的区别
对缓冲区的读写操作首先要知道缓冲区的下限.上限和当前位置.下面这些变量的值对Buffer类中的某些操作有着至关重要的作用: limit:所有对Buffer读写操作都会以limit变量的值作为上限. p ...
- java 子接口中定义与父接口相同的方法
今天碰到一个很有意思的问题,在java中如果子接口中定义了与父接口中已经有的方法会发生什么事情呢?比如: interface IRunnable extends Runnable{ void run( ...
- Java 非静态内部类中可以定义静态变量或方法吗?
如图: 这个问题的答案是不可以 由于内部类的实例化是由外部类实例化之后加载的,如果外部类还没有实例化,这时候调用内部类的静态成员,此时内部类还没有被加载,却要开始创建静态成员,这是矛盾的,所以java ...
- java之字符串中查找字串的常见方法
1.int indexOf(String str) :返回第一次出现的指定子字符串在此字符串中的索引. int indexOf(String str, int startIndex):从指定 ...
随机推荐
- java③
1.变量是什么? 变量====>一个数据在内存中 存储空间的表示! 在程序运行期间可以发生变化! *变量名 可以 迅速的从内存中 查询出 指定的变量! 2.数据类型: 数据类型 一共分为两种: ...
- oo作业总结报告
oo第一次博客 以前从未真正的写过Java代码,接触Java也只是寒假的时候简单的看了看语法,不懂该如何面向对象,但没事,心里不惧,想着什么都是可以学的(直到真正开始写工程的时候,才发现自己还是太天真 ...
- PC/FORTH 编辑程序
body, table{font-family: 微软雅黑} table{border-collapse: collapse; border: solid gray; border-width: 2p ...
- main.jsbundle 脱离掉本地服务
我们在本地调试的时候,可以使用index.js来开启本地服务,在局域网内运行app. 但是你会发现一旦你脱离了这个局域网就会造成app无法显示 这时候我们使用main.jsbundle 1.在Reac ...
- VSTO:使用C#开发Excel、Word【5】
<Visual Studio Tools for Office: Using C# with Excel, Word, Outlook, and InfoPath >——By Eric C ...
- 深入理解java虚拟机---java虚拟机内存管理(五)
1.深入理解java虚拟机 总图: 1.线程共享区: 2.线程独占区: 1.程序计数器 理解为当前线程锁执行的字节码的行号指示器,程序计数器没有内存异常错误.
- IEDA中彻底删除项目
删除项目一向比较奇葩,因为当你点击到 该项目名称-->右键 时,并没有 delete 选项,导致我们不知道怎么删除,查找多方文档,得到以下解决: (1)将鼠标移到要删除的 项目名称 上,单击并按 ...
- SUSE_LINUX 11 SP3 安装 IBM MQ 7.5
0.环境介绍 mq7.5 suse linux 11 1. 上传安装包 上传安装包到 softWare/CI79IML.tar.gz 2. 安装证书 sh ./mqlicense.sh 输入 1 同意 ...
- day73 母版 中间件
关于母版自定义的问题 模板引擎: 基本实用{{k1}} if for 在页面传参数 并判断 通过母版进行交互 一 模板中自定义函数:操作步骤 1在已经注册的App中创建一个名字叫templat ...
- mysql增删改查练习
Mysql增删改查sql语句练习 关于数据库的一些操作: 进入mysql 命令行: mysql -uroot –p 查看所有数据库: show databases; 创建数据库: create dat ...