在看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就没有实现接口中声明的方法,语义就不正确了,所以编译器才会自动生成桥接方法,来保证兼容性。
相关推荐
本篇将详细介绍一种非HTTP桥接的方式——利用`JavaBridge.jar`实现PHP与Java之间的交互。这种方式不仅避免了HTTP请求所带来的额外开销,还能够更灵活地管理数据传输。 #### 二、准备工作 1. **安装并配置JDK** - ...
Bridge ( 桥接模式 ) Composite ( 组合模式 ) Decorator ( 装饰模式 ) Facade ( 外观模式 ) Flyweight ( 享元模式 ) Proxy ( 代理模式 ) Chain of Responsibility ( 责任链模式 ) Command ( 命令模式 ) ...
结构型模式如适配器(Adapter)、装饰器(Decorator)、代理(Proxy)、桥接(Bridge)和组合(Composite),则关注如何将不同组件组合成更复杂的结构,以实现新的功能。行为型模式如策略(Strategy)、观察者...
12. 模板方法模式(Template Method):定义一个操作中的算法骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 13. 观察者模式(Observer):定义对象间的一对...
- RMI (Remote Method Invocation): 远程方法调用机制。 ### 创建型模式 创建型模式关注于对象的创建机制,这些模式尝试在创建新对象时提供一定的灵活性。 #### 8. 抽象工厂模式(Abstract Factory) 抽象工厂模式...
- **桥接**(Bridge):将抽象部分与它的实现部分分离,使它们都可以独立地变化。 - **组合**(Composite):将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有...
Java操作Word文件主要涉及到的是对Microsoft Office文档的处理,这在很多企业级应用中非常常见,比如自动化报告生成、数据导入导出等。在Java中,由于Java本身并不直接支持与Windows API交互,因此需要借助第三方库...
7. Bridge(桥接) 8. Composite(组合) 9. Decorator(装饰) 10. Facade(外观) 11. Flyweight(享元) 12. Proxy(代理) 行为型 13. Interpreter(解释器) 14. Template Method(模板方法) 15. Chain of ...
Jacob全称为Java COM Bridge,是一个Java到COM桥接库,它允许Java代码调用COM组件,如Microsoft Office套件中的Word应用。在Java中直接操作Word文档,包括读取、编辑和展示,通常需要用到这样的库。Jacob配置文件是...
结构型模式如适配器(Adapter)、桥接(Bridge)、组合(Composite)、装饰(Decorator)、外观(Facade)、享元(Flyweight)和代理(Proxy),主要关注如何组合不同的类和对象以实现新的功能。行为型模式如策略...
桥接模式(Bridge)将抽象部分与其实现部分分离,使它们可以独立变化,提供更大的灵活性。这些模式的组合使用可以帮助开发者构建更复杂、更健壮的系统。 在实际项目中,设计模式的应用可以提高代码的可读性、可维护...
结构型模式涉及到如何组合对象和类,以形成更大的结构,例如适配器模式(Adapter)、装饰器模式(Decorator)、代理模式(Proxy)和桥接模式(Bridge)。这些模式有助于解决不同组件之间的兼容性问题,使系统更易于...
9. **桥接模式(Bridge)**:将抽象部分与它的实现部分分离,使它们都可以独立地变化。在Java中,通过接口和实现类的组合实现。 10. **代理模式(Proxy)**:为其他对象提供一种代理以控制对这个对象的访问。Java中...
在《Java设计模式》这本书的模拟试题及其参考答案中,涉及了多条设计模式的知识点,以及它们的应用场景和实现方法。现在将这些知识点详细地解释如下: 1. 开闭原则(Open-Closed Principle, OCP): 开闭原则是面向...
在这个压缩包中,提到了三种主要的设计模式:Bridge模式、Command模式和FactoryMethod以及SimpleFactory模式。 1. Bridge模式: Bridge模式是一种结构型设计模式,它将抽象部分与其实现部分分离,使得它们可以独立...
在Java的`java.io.InputStream`和`java.io.OutputStream`中,`read()`和`write()`方法是模板方法。 12. 观察者模式(Observer Pattern):定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于...
22. **模板方法模式(Template Method)**:定义一个操作中的算法骨架,而将一些步骤延迟到子类中。Java中通过抽象类和具体子类实现。 23. **访问者模式(Visitor)**:表示一个作用于某对象结构中的各元素的操作。...
结构型模式如适配器(Adapter)、装饰器(Decorator)、桥接(Bridge)等,关注于如何组合和构建类或对象,以实现更复杂的功能。行为型模式如策略(Strategy)、观察者(Observer)、职责链(Chain of ...
在Java中,通常通过私有构造函数和静态工厂方法实现。这种方式可以避免多线程环境下多个实例的产生,常用于配置中心、日志服务等。 2. **工厂模式(Factory Method)**:定义一个创建对象的接口,但让子类决定实例...