Java编程:浅析泛型类型中的桥接方法

Posted 志波同学

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java编程:浅析泛型类型中的桥接方法相关的知识,希望对你有一定的参考价值。

一、什么是桥接方法

在介绍桥接方法之前,我先介绍下泛型。在 JDK1.5 中引入了泛型,泛型类型是基于原始类型、类型擦除原理进行实现的。

原始类型
Java总是会自动的为泛型类型提供一个相应的原始类型。所谓原始类型就是是指泛型的第一个限定类型(从左向右),无限定类型泛型的原始类型默认为Object。

类型擦除
Java中泛型的实现原理是类型擦除(type erasure)。类型擦除是在编译器进行代码编译这个阶段进行的,在编译的时候泛型的类型参数会被原始类型(raw type)所替代。

进入正题,给出桥接方法的定义。桥接方法是在父类、子类的继承场景中出现的。父类是泛型类,且在该类中存在泛型方法。子类继承父类,并实现泛型方法。如果在子类实现中不包含父类经过类型擦除后生成的原始类型方法,则编译器会自动将该原始类型方法添加到子类中。这个被添加的原始类型方法我们称之为桥接方法。

二、桥接示例

1、定义泛型基类 Base

public interface Base<T> 
    /**
     *
     * @param t
     */
    void process(T t);

通过 javap -p Base.class 命令查看编译后的类信息,可以发现父类没有增加方法。

 javap -p Base.class 
Compiled from "Base.java"
public interface org.learn.method.Base<T> 
	public abstract void process(T);

2、定义具体实现类 ConcreteString

public class ConcreteString implements Base<String> 
    @Override
    public void process(String val) 
        System.out.println("i am a string.");
    

通过 javap -p ConcreteString.class 命令查看编译后的类信息,我们可以发现,在 ConcreteString 类中多了一个方法 public void process(java.lang.Object); ,它就是我们本文要讨论的桥接方法。

zhibo@zhibo-mac method % javap -p ConcreteString.class 
Compiled from "ConcreteString.java"
public class org.learn.method.ConcreteString implements org.learn.method.Base<java.lang.String> 
	public org.learn.method.ConcreteString();
	public void process(java.lang.String);
	public void process(java.lang.Object);

3、定义扩展类 ConcreteStringExt

public class ConcreteStringExt extends ConcreteString
    public void init()
        System.out.println("i am ConcreteStringExt");
    

通过 javap -p ConcreteStringExt.class命令查看编译后的类信息,我们可以发现,在 ConcreteStringExt 类中并没有生成桥接方法。

javap -p ConcreteStringExt.class
Compiled from "ConcreteStringExt.java"
public class org.learn.method.ConcreteStringExt extends org.learn.method.ConcreteString 
  public org.learn.method.ConcreteStringExt();
  public void init();

4、在 ConcreteStringExt 在中重写 public void process(String val) 方法:

public class ConcreteStringExt extends ConcreteString
    @Override
    public void process(String val) 
        System.out.println("i am a string.");
    
    public void init()
        System.out.println("i am ConcreteStringExt");
    

通过 javap -p ConcreteStringExt.class命令查看编译后的类信息,我们可以发现,此时在 ConcreteStringExt 类中生成了桥接方法。

javap -p ConcreteStringExt.class
Compiled from "ConcreteStringExt.java"
public class org.learn.method.ConcreteStringExt extends org.learn.method.ConcreteString 
	public org.learn.method.ConcreteStringExt();
	public void process(java.lang.String);
	public void init();
	public void process(java.lang.Object);


因此可以推测,桥接方法是伴随泛型方法而生的,在继承关系中,如果某个子类覆盖了泛型方法,则编译器会在该子类自动生成桥接方法。

三、为什么要生成桥接方法

解释一:
Base 类经过类型擦除转换为原始类型后,会生成 public void process(java.lang.Object); 方法,如果子类不实现该方法,则不满足 Java 接口语义。我认为这种解释有些牵强,因为在 java 语言规范中,是不允许在一个类中定义 public void process(String val)public void process(java.lang.Object); 这样的两个方法的。

解释二:
所有的 Base 类都定义了相同的 process方法,Base 类经过类型擦除转换为原始类型后,会生成 public void process(java.lang.Object); 方法,在子类定义了泛型的具体类型,导致子类中的 process 方法变化为 public void process(String val),此时父类的 public void process(java.lang.Object); 是没有被实现的,为了解决这个问题,Java 编译器通过桥接的方式实现了 public void process(java.lang.Object); 方法。 这样就保证了 Base 类的子类具有相同的一致的方法 public void process(java.lang.Object); 。在访问泛型对象时,通过父类方法 process 进行统一调用,而不需要关注子类的具体实现。

四、方法桥接了什么

通过下图可以清晰的看到, public void process(java.lang.Object); 经过类型转换后最终调用了 public void process(String val)

看到这里,就可以理解第二章中观察到的现象了。桥接方法是伴随泛型方法的实现而生,因此要把泛型方法的实现桥接到原型方法中。

“因此可以推测,桥接方法是伴随泛型方法而生的,在继承关系中,如果某个子类覆盖了泛型方法,则编译器会在该子类自动生成桥接方法。”

由此也可以推测出,桥接方法的生成与父类是接口、具体类、抽象类没有关系,只有父类的泛型方法的实现类有关系,在哪个类中实现泛型方法,就会生成对应的桥接方法。

五、如何判断桥接方法

通过 method.isBridge() 可以判定是否为桥接方法。

public class MainInteger 
    public static void main(String[] args) 
        try 
            ConcreteInteger concreteInteger = new ConcreteInteger();
            Method method = ConcreteInteger.class.getMethod("process", Integer.class);
            method.invoke(concreteInteger, 1);
            System.out.println(method.isBridge());

            method = ConcreteInteger.class.getMethod("process", Object.class);
            method.invoke(concreteInteger, 1);
            System.out.println(method.isBridge());
         catch (Exception e) 
            e.printStackTrace();
        
    

六、使用场景

在 Mybatis中, MapperAnnotationBuilder 类的 parse 方进行了桥接方法的判定。在进行方法的反射访问时,我们也需要考虑桥接方法是否处理,比如在日志拦截器中,如果不排除桥接方法,在调用桥接方法时就会打印两份日志。

文章内容仅代表个人观点,如有不正之处,欢迎批评指正,谢谢大家。

以上是关于Java编程:浅析泛型类型中的桥接方法的主要内容,如果未能解决你的问题,请参考以下文章

Java编程:浅析泛型类型中的桥接方法

Java编程:浅析泛型类型中的桥接方法

[C/C++]浅析C++中的模板

Java的桥接方法和BridgeMethodResolver使用

Java的桥接方法和BridgeMethodResolver使用

Java的桥接方法和BridgeMethodResolver使用