java中的桥接方法
导语
在看spring-mvc的源码的时候,看到在解析handler方法时,有关于获取桥接方法代码,不明白什么是桥接方法,经过查找资料,终于理解了什么是桥接方法。
什么是桥接方法
桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法。
我们可以通过Method.isBridge()方法来判断一个方法是否是桥接方法,在字节码中桥接方法会被标记为ACC_BRIDGE和ACC_SYNTHETIC,其中ACC_BRIDGE用于说明这个方法是由编译生成的桥接方法,ACC_SYNTHETIC说明这个方法是由编译器生成,并且不会在源代码中出现。可以查看jvm规范中对这两个access_flag的解释。
有如下3个问题:
- 什么时候会生成桥接方法
- 为什么要生成桥接方法
- 如何通过桥接方法获取实际的方法
什么时候会生成桥接方法
那什么时候编译器会生成桥接方法呢?可以查看JLS中的描述。
就是说一个子类在继承(或实现)一个父类(或接口)的泛型方法时,在子类中明确指定了泛型类型,那么在编译时编译器会自动生成桥接方法(当然还有其他情况会生成桥接方法,这里只是列举了其中一种情况)。如下所示:
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系列:关于Java中的桥接方法
这两天在看<Java核心技术 卷1>的泛型相关章节,其中说到了在泛型子类中override父类的泛型方法时,编译器会自动生成一个桥接方法,这块有点看不明白. 书上的例子代码如下: publ ...
- 初识Java泛型以及桥接方法
泛型的由来 在编写程序时,可能会有这样的需求:容器类,比如java中常见的list等.为了使容器可以保存多种类型的数据,需要编写多种容器类,每一个容器类中规定好了可以操作的数据类型.此时可能会有Int ...
- 浅谈Java中的hashcode方法
哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: 1 public native int hashCode(); 根据 ...
- 千万不要误用 java 中的 HashCode 方法
刚才debug追堆栈的时候发现一个很奇怪的问题 我用IE8和Google的浏览器访问同一个地址 Action的 scope="session" 也设置了 而且两个浏览器提交的参数m ...
- Java中的toString()方法
Java中的toString()方法 目录 Java中的toString()方法 1. 对象的toString方法 2. 基本类型的toString方法 3. 数组的toString ...
- Java中的main()方法详解
在Java中,main()方法是Java应用程序的入口方法,也就是说,程序在运行的时候,第一个执行的方法就是main()方法,这个方法和其他的方法有很大的不同,比如方法的名字必须是main,方法必须是 ...
- 【转】浅谈Java中的hashcode方法(这个demo可以多看看)
浅谈Java中的hashcode方法 哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native i ...
- JAVA中的finalize()方法
[转]JAVA中的finalize()方法 今天早上看Thinking in java的[第四章 初始化和清除].[ 清除:终结和垃圾回收]的时候, 看到了这个东西. 用于清理滴... 当然,这个方 ...
- Java中的native方法
博客引用地址:Java中的native方法 今天花了两个小时把一份关于什么是Native Method的英文文章好好了读了一遍,以下是我依据原文的理解. 一. 什么是Native Method 简单地 ...
随机推荐
- 关于.NET中迭代器的实现以及集合扩展方法的理解
在C#中所有的数据结构类型都实现IEnumerable或IEnumerable<T>接口(实现迭代器模式),可以实现对集合遍历(集合元素顺序访问).换句话可以这么说,只要实现上面这两个接口 ...
- hdu 6703 array(权值线段树)
Problem Description You are given an array a1,a2,...,an(∀i∈[1,n],1≤ai≤n). Initially, each element of ...
- 【noi 2.6_8786】方格取数(DP)
题意:N*N的方格图每格有一个数值,要求从左上角每步往右或往下走到右下角,问走2次的最大和. 解法:走一次的很好想,而走2次,不可误以为先找到最大和的路,再找剩下的最大和的路就是正解.而应该认清动态规 ...
- Codeforces #637 div2 B. Nastya and Door
题意:给你一个数组a,定义:若a[i]>a[i]&&a[i]>a[i-1],则a[i]为峰值,求长度为k的区间内峰值最多能为多少,并输出这个区间的左端点(区间需要将峰的左边 ...
- 用了很多年Dubbo,连Dubbo线程池监控都不知道,觉得自己很厉害?
前言 micrometer中自带了很多其他框架的指标信息,可以很方便的通过prometheus进行采集和监控,常用的有JVM的信息,Http请求的信息,Tomcat线程的信息等. 对于一些比较活跃的框 ...
- 使用networkx库可视化对称矩阵的网络图形
首先我的数据是.mat格式,讲讲如何用python读取.mat格式的数据(全套来) 我是python3版本 第一种读取方法: import h5py features_struct = h5py.Fi ...
- [Python] Pandas的delete、drop函数的用法
目录 drop函数 Axis(轴)含义 drop用法实验 delete函数 drop函数 DataFrame.drop(labels=None, axis=0, index=None, columns ...
- Kubernets二进制安装(7)之部署主控节点服务--apiserver简介
API Server简介 Kubernetes API Server提供了K8S各类资源对象(如:pod.RC.Service等)的增删改查及watch等HTTP Rest接口,是整个系统的数据总线和 ...
- Kubernetes安装EFK教程(非存储持久化方式部署)
1.简介 这里所指的EFK是指:ElasticSearch,Fluentd,Kibana ElasticSearch Elasticsearch是一个基于Apache Lucene的开源搜索和数据分析 ...
- 2.使用jenkins自动构建并发布应用到k8s集群
作者 微信:tangy8080 电子邮箱:914661180@qq.com 更新时间:2019-06-21 14:39:01 星期五 欢迎您订阅和分享我的订阅号,订阅号内会不定期分享一些我自己学习过程 ...