详解Java的桥接方法

什么是桥接方法?

Java中的桥接方法(Bridge Method)是一种为了实现某些Java语言特性而由编译器自动生成的方法。

我们可以通过Method类的isBridge方法来判断一个方法是否是桥接方法。

在字节码文件中,桥接方法会被标记为ACC_BRIDGEACC_SYNTHETIC,其中ACC_BRIDGE用于表示该方法是由编译器产生的桥接方法,ACC_SYNTHETIC用于表示该方法是由编译器自动生成。

什么时候生成桥接方法?

为了实现哪些Java语言特性会生成桥接方法?最常见的两种情况就是协变返回值类型和类型擦除,因为它们导致了父类方法的参数和实际调用的方法参数类型不一致。下面我们通过两个例子更好地理解一下。

协变返回类型

协变返回类型是指子类方法的返回值类型不必严格等同于父类中被重写的方法的返回值类型,而可以是更 "具体" 的类型。

在Java 1.5添加了对协变返回类型的支持,即子类重写父类方法时,返回的类型可以是子类方法返回类型的子类。下面看一个例子:

public class Parent {
  Number get() {
    return 1;
  }
}
public class Child extends Parent {

  @Override
  Integer get() {
    return 1;
  }
}

Child类重写其父类Parent的get方法,Parent的get方法返回类型为Number,而Child类中get方法返回类型为Integer。

将这段代码进行编译,再反编译:

javac Child.java
javap -v -c Child.class

结果如下:

public class Child extends Parent
......省略部分结果......
 java.lang.Integer get();
  descriptor: ()Ljava/lang/Integer;
  flags:
  Code:
   stack=1, locals=1, args_size=1
     0: iconst_1
     1: invokestatic #2         // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     4: areturn
   LineNumberTable:
    line 5: 0

 java.lang.Number get();
  descriptor: ()Ljava/lang/Number;
  flags: ACC_BRIDGE, ACC_SYNTHETIC
  Code:
   stack=1, locals=1, args_size=1
     0: aload_0
     1: invokevirtual #3         // Method get:()Ljava/lang/Integer;
     4: areturn
   LineNumberTable:
    line 1: 0

从上面的结果可以看到,有一个方法java.lang.Number get(), 在源码中是没有出现过的,是由编译器自动生成的,该方法被标记为ACC_BRIDGE和ACC_SYNTHETIC,就是我们前面所说的桥接方法。

这个方法就起了一个桥接的作用,它所做的就是把对自身的调用通过invokevirtual指令再调用方法java.lang.Integer get()。

编译器这么做的原因是什么呢?因为在JVM方法中,返回类型也是方法签名的一部分,而桥接方法的签名和其父类的方法签名一致,以此就实现了协变返回值类型。

类型擦除

泛型是Java 1.5才引进的概念,在这之前是没有泛型的概念的,但泛型代码能够很好地和之前版本的代码很好地兼容,这是为什么呢?

这是因为,在编译期间Java编译器会将类型参数替换为其上界(类型参数中extends子句的类型),如果上界没有定义,则默认为Object,这就叫做类型擦除。

当一个子类在继承(或实现)一个父类(或接口)的泛型方法时,在子类中明确指定了泛型类型,那么在编译时编译器会自动生成桥接方法,例如:

public class Parent<T> {

  void set(T t) {
  }
}
public class Child extends Parent<String> {

  @Override
  void set(String str) {
  }
}

Child类在继承其父类Parent的泛型方法时,明确指定了泛型类型为String,将这段代码进行编译,再反编译:

public class Child extends Parent<java.lang.String>
......省略部分结果......
 void set(java.lang.String);
  descriptor: (Ljava/lang/String;)V
  flags:
  Code:
   stack=0, locals=2, args_size=2
     0: return
   LineNumberTable:
    line 5: 0

 void set(java.lang.Object);
  descriptor: (Ljava/lang/Object;)V
  flags: 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 set:(Ljava/lang/String;)V
     8: return
   LineNumberTable:
    line 1: 0

从上面的结果可以看到,有一个方法void set(java.lang.Object), 在源码中是没有出现过的,是由编译器自动生成的,该方法被标记为ACC_BRIDGE和ACC_SYNTHETIC,就是我们前面所说的桥接方法。

这个方法就起了一个桥接的作用,它所做的就是把对自身的调用通过invokevirtual指令再调用方法void set(java.lang.String)。

编译器这么做的原因是什么呢?因为Parent类在类型擦除之后,变成这样:

public class Parent<Object> {

  void set(Object t) {
  }
}

编译器为了让子类有一个与父类的方法签名一致的方法,就在子类自动生成一个与父类的方法签名一致的桥接方法。

如何查找桥接方法的实际方法

在Spring Framework中已经实现了查找桥接方法的实际方法的功能,就在spring-core模块中的BridgeMethodResolver类中,像这样直接使用就行了:

method = BridgeMethodResolver.findBridgedMethod(method);

findBridgedMethod方法是怎么实现的呢?我们来分析一下源码(spring-core的版本为5.2.8.RELEASE):

public static Method findBridgedMethod(Method bridgeMethod) {
  // 如果不是桥连方法,就直接返回原方法。
	if (!bridgeMethod.isBridge()) {
		return bridgeMethod;
	}
  // 先从本地缓存读取,缓存中有则直接返回。
	Method bridgedMethod = cache.get(bridgeMethod);
	if (bridgedMethod == null) {
		List<Method> candidateMethods = new ArrayList<>();
    // 以方法名称和入参个数相等为筛选条件。
		MethodFilter filter = candidateMethod ->
				isBridgedCandidateFor(candidateMethod, bridgeMethod);
    // 递归该类及其所有父类上的所有方法,符合筛选条件就添加进来。
		ReflectionUtils.doWithMethods(bridgeMethod.getDeclaringClass()
      , candidateMethods::add, filter);
		if (!candidateMethods.isEmpty()) {
      // 如果符合筛选条件的方法个数为1,则直接采用;
      // 否则,调用searchCandidates方法再次筛选。
			bridgedMethod = candidateMethods.size() == 1 ?
					candidateMethods.get(0) :
					searchCandidates(candidateMethods, bridgeMethod);
		}
    // 如果找不到实际方法,则返回原来的桥连方法。
		if (bridgedMethod == null) {
			// A bridge method was passed in but we couldn't find the bridged method.
			// Let's proceed with the passed-in method and hope for the best...
			bridgedMethod = bridgeMethod;
		}
    // 把查找的结果放入内存缓存。
		cache.put(bridgeMethod, bridgedMethod);
	}
	return bridgedMethod;
}

我们再看一下再次筛选的searchCandidates方法是如何实现的:

private static Method searchCandidates(List<Method> candidateMethods, Method bridgeMethod) {
	if (candidateMethods.isEmpty()) {
		return null;
	}
	Method previousMethod = null;
	boolean sameSig = true;
  // 遍历候选方法的列表
	for (Method candidateMethod : candidateMethods) {
    // 对比桥接方法的泛型类型参数和候选方法是否匹配,如果匹配则直接返回该候选方法。
		if (isBridgeMethodFor(bridgeMethod, candidateMethod, bridgeMethod.getDeclaringClass())) {
			return candidateMethod;
		}
		else if (previousMethod != null) {
      // 如果不匹配,则判断所有候选方法的参数列表是否相等。
			sameSig = sameSig && Arrays.equals(candidateMethod.getGenericParameterTypes()
        , previousMethod.getGenericParameterTypes());
		}
		previousMethod = candidateMethod;
	}
  // 如果所有候选方法的参数列表全相等,则返回第一个候选方法。
	return (sameSig ? candidateMethods.get(0) : null);
}

总结以上源码就是,通过判断方法名、参数的个数以及泛型类型参数来获取桥接方法的实际方法。

以上就是详解Java的桥接方法的详细内容,更多关于java 桥接方法的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java 中桥接模式——对象结构型模式的实例详解

    Java  中桥接模式--对象结构型模式的实例详解 一.意图 将抽象部分与它的实现部分分离,使他们都可以独立的变化. 二.适用性 以下一些情况使用Bridge模式 你不希望在抽象和它的实现部分之间有一个固定的绑定关系.例如这种情况可能因为,在程序运行时刻实现部分应可以被选择或者切换. 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充.这时Bridge模式使你可以对不同的抽象接口和实现部分进行组合,并分别对他们进行扩充. 对一个抽象的实现部分的修改应对客户不产生影响,即客户代码不必重新编译

  • Java使用桥接模式实现开关和电灯照明功能详解

    本文实例讲述了Java使用桥接模式实现开关和电灯照明功能.分享给大家供大家参考,具体如下: 一.模式定义 桥接模式,也称桥梁模式,在软件系统中,由于自身的逻辑,具有两个或多个维度的变化,如何应对这种多维度的变化,桥接模式使得软件系统能够轻松地沿着多个方向进行变化,而又不引入额外的复杂度. 桥接模式三个关键词为:抽象化,实现化,脱耦 二.模式举例 1 桥接模式分析方法 我们借用电灯照明来说明该模式. 不使用继承,使用对象组合的方式,将开关和电灯的强关联关系变成弱关联关系. 2 桥接模式静态类模型

  • Java结构型设计模式中的适配器模式与桥接模式解析

    适配器模式 定义 适配器模式(英语:adapter pattern)有时候也称包装样式或者包装.将一个类的接口转接成用户所期待的.一个适配使得因接口不兼容而不能在一起工作的类工作在一起. 有两类适配器模式: 1. 对象适配器模式 - 对象适配器通过关联满足用户期待接口,还降低了代码间的不良耦合.在工作中推荐使用"对象适配". 2. 类适配器模式 - 这种适配器模式下,适配器继承自已实现的类(一般多重继承),java中没有多重继承,所以这里不做介绍. 实现 1. Target - 定义C

  • Java桥接模式实例详解【简单版与升级版】

    本文实例讲述了Java桥接模式.分享给大家供大家参考,具体如下: 桥接模式简单版 一 代码 class Meal { protected MealImp imp; public Meal() { imp = new AmericanMealImp(); } public Meal(String type) { if (type.equals("American")) imp = new AmericanMealImp(); if (type.equals("Italian&q

  • Java桥接模式原理及用法解析

    桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化.这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦. 这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类.这两种类型的类可被结构化改变而互不影响. 我们通过下面的实例来演示桥接模式(Bridge Pattern)的用法.其中,可以使用相同的抽象类方法但是不同的桥接实现类,来画出不同颜色的圆. 介绍 意图:将抽象部分与实现部分分离,使它们都可以独立的变化. 主要解决:在有多

  • 23种设计模式(9) java桥接模式

    介绍 桥接模式 (Bridge)将抽象部分与实现部分分离,使它们都可以独立的变化. 桥接模式是一种结构式模式. 结构 代码实现 Implementor : 定义实现接口. interface Implementor { // 实现抽象部分需要的某些具体功能 public void operationImpl(); } Abstraction : 定义抽象接口. abstract class Abstraction { // 持有一个 Implementor 对象,形成聚合关系 protected

  • 轻松掌握Java桥接模式

    定义:将抽象部分与实现部分分离,使它们都可以独立的变化. 特点:桥接模式基于类的最小设计原则,通过使用封装,聚合以及继承等行为来让不同的类承担不同的责任.它的主要特点是把抽象(abstraction)与行为实现(implementation)分离开来,从而可以保持各部分的独立性以及应对它们的功能扩展. 企业级开发和常用框架中的应用:多继承的结构 只是从定义理解桥接模式,很难理清它的作用,下面举个例子来说明: 电脑城卖电脑,电脑种类分了平板电脑,笔记本电脑,台式电脑:电脑品牌又分了联想,戴尔,华硕

  • java设计模式之桥接模式(Bridge)

    概述 桥接模式一种结构型模式,它主要应对的是:由于实际的需要,某个类具有两个或以上的维度变化,如果只是用继承将无法实现这种需要,或者使得设计变得相当臃肿. 桥接模式的做法是把变化的部分抽象出来,使变化部分与主类分离开来,从而将多个维度的变化彻底分离.最后,提供一个管理类来组合不同维度上的变化,通过这种组合来满足业务的需要. UML结构图 代码示例 package interview; interface Implementor{ void operationImpl(); } abstract

  • Java设计模式之桥接模式实例详解

    本文实例讲述了Java设计模式之桥接模式.分享给大家供大家参考,具体如下: 概念: 桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化. 桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量. 什么情况下会用桥接模式? 简单的说就是我们在抽象对象的特征时,对象的特征属性又很抽象,不得不把属性再次抽象. 否则的话,具体子类的数量将会成几何增长,而且不易扩展.没办法维护现有代码. 举例,我们在抽象手机这二个对象时,它的几个属性,如

  • 详解Java的桥接方法

    什么是桥接方法? Java中的桥接方法(Bridge Method)是一种为了实现某些Java语言特性而由编译器自动生成的方法. 我们可以通过Method类的isBridge方法来判断一个方法是否是桥接方法. 在字节码文件中,桥接方法会被标记为ACC_BRIDGE和ACC_SYNTHETIC,其中ACC_BRIDGE用于表示该方法是由编译器产生的桥接方法,ACC_SYNTHETIC用于表示该方法是由编译器自动生成. 什么时候生成桥接方法? 为了实现哪些Java语言特性会生成桥接方法?最常见的两种

  • 详解Java中native方法的使用

    今天在网上学习时碰到有关于 native修饰符所修饰的方法,上网查了查,觉得很有意思记录一下 1.native简介 简单地讲,一个Native Method就是一个java调用非java代码的接口.一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C.这个特征并非java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "C"告知C++编译器去调用一个C的函数. native是与C++联合开发的时候用的!使用nat

  • 详解Java从工厂方法模式到 IOC/DI思想

    前言 简单工厂的本质是选择实现,说白了是由一个专门的类去负责生产我们所需要的对象,从而将对象的创建从代码中剥离出来,实现松耦合.我们来看一个例子: 我们要创建一个文件导出工具 public interface FileOper{ public Boolean exceptFile(String data); } public class XMLFileOp implment FileOper{ public Boolean exceptFile(String data){ System.out.

  • 详解Java CompletableFuture使用方法以及与FutureTask的区别

    目录 futureTask 创建异步任务 创建任务 1. .supplyAsync 2. .runAsync 异步回调 1. .thenApply 2. .thenAccept 3. .exceptionally 4. .whenComplete 组合处理 总的来说简洁了FutureTask与线程池的配合使用 没啥太大区别吧我觉得, 使用方法不一样, 多了一些方法 ??? futureTask 创建异步任务 FutureTask<String> stringFutureTask = new F

  • 详解Java多线程tryLock()方法使用

    tryLock(long time, TimeUnit unit) 的作用在给定等待时长内锁没有被另外的线程持有,并且当前线程也没有被中断,则获得该锁,通过该方法可以实现锁对象的限时等待. package com.wkcto.lock.reentrant; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; /** *tryLock(long time, TimeUnit u

  • 详解java 单例模式及方法总结

    java设计模式--单例模式  单例设计模式 Singleton是一种创建型模式,指某个类采用Singleton模式,则在这个类被创建后,只可能产生一个实例供外部访问,并且提供一个全局的访问点. 核心知识点如下: (1) 将采用单例设计模式的类的构造方法私有化(采用private修饰). (2) 在其内部产生该类的实例化对象,并将其封装成private static类型. (3) 定义一个静态方法返回该类的实例. /** * 方法一 * 单例模式的实现:饿汉式,线程安全 但效率比较低 */ pu

  • 详解Java设计模式之桥接模式

    目录 一.什么是桥接模式: 二.UML结构图 三.代码实现 四.JDBC源码解析-桥接模式 1.源码分析 2.源码类 3.对 JDBC 的观点 一.什么是桥接模式: 桥接,顾名思义,就是用来连接两个部分,使得两个部分可以互相通讯,桥接模式的作用就是为被分离的抽象部分和实现部分搭桥.在现实生活中一个物品在搭配不同的配件时会产生不同的动作和结果,例如一辆赛车搭配的是硬胎或者是软胎就能够在干燥的马路上行驶,而如果要在下雨的路面行驶,就需要搭配雨胎了,这种根据行驶的路面不同,需要搭配不同的轮胎的变化的情

  • 详解Java编写并运行spark应用程序的方法

    我们首先提出这样一个简单的需求: 现在要分析某网站的访问日志信息,统计来自不同IP的用户访问的次数,从而通过Geo信息来获得来访用户所在国家地区分布状况.这里我拿我网站的日志记录行示例,如下所示: 121.205.198.92 - - [21/Feb/2014:00:00:07 +0800] "GET /archives/417.html HTTP/1.1" 200 11465 "http://shiyanjun.cn/archives/417.html/" &qu

  • 详解java.lang.reflect.Modifier.isInterface()方法

    详解java.lang.reflect.Modifier.isInterface()方法 java.lang.reflect.Modifier.isInterface(int mod)方法判断如果给定mod参数包含final修饰符,则返回true,否则返回false. 声明 以下是java.lang.reflect.Modifier.isInterface()方法的声明. public static boolean isInterface(int mod) 参数 mod - 一组修饰符. 返回值

  • 详解Java 本地接口 JNI 使用方法

    详解Java 本地接口 JNI 使用方法 对于Java程序员来说,Java语言的好处和优点,我想不用我说了,大家自然会说出很多一套套的.但虽然我们作为java程序员,但我们不得不承认java语言也有一些它本身的缺点.比如在性能.和底层打交道方面都有它的缺点.所以java就提供了一些本地接口,他主要的作用就是提供一个标准的方式让java程序通过虚拟机与原生代码进行交互,这也就是我们平常常说的java本地接口(JNI--java native Interface).它使得在 Java 虚拟机 (VM

随机推荐