美文网首页
Java泛型中的桥方法(Bridge Method)

Java泛型中的桥方法(Bridge Method)

作者: haoyanbing | 来源:发表于2019-04-21 11:37 被阅读0次

前几天在看 Java 泛型的时候,发现了一个有趣的现象。就是在某些情况下,编译器在编译我们的类文件的时候会帮我们自动生成某些方法,称作桥方法。

我们知道 Java 中的泛型在编译为 class 的时候会把泛型擦除,也就是说你写的 <T> 到最后 class 文件中其实都是 Object,看下面代码示例:

public class A<T> {
    private T value;
    public T getValue() {
        return value;
    }
    public void setValue(T value) {
        this.value = value;
    }
}
// ------------编译后-------------
public class A {
    private Object value;
    public Object getValue() {
        return value;
    }
    public void setValue(Object value) {
        this.value = value;
    }
}

可以看出,Java 中的泛型在编译后都变成了 Object,也可以说 Java 中的泛型其实是编译器为我们做了优化,虚拟机中是没有泛型的。

那我们接着看下面这段代码:

public class B extends A<String> {
    @Override
    public void setValue(String value) {
        System.out.println("---B.setValue()---");
    }
}

我们写了一个 B 类,继承自 A 类,并重写了 setValue 方法。

我们来思考一个问题,按我们上面所说的 Java 泛型的擦除机制,实际 A 类中 setValue 方法应该是这样的:

// A 类中的 setValue 方法
public void setValue(Object value){
    this.value = value;
}

这个时候问题出来了,我们发现 B 类中的 setValue 方法参数与 A 类中的 setValue 方法参数不一样。按照 Java 重写方法的规则,B 类中的 setValue 方法实际上并没有重写父类中的方法,而是重载。

所以实际上 B 类中应该是有两个 setValue 方法,一个自己的,一个继承来的:

// 自己的
public void setValue(String value){...}
// 从父类继承的
public void setValue(Object value){...}

所以在某些场景,比如反射调用 B 类中的方法的时候,就有可能会调用到从父类继承的那个 setValue 方法。

这个时候就会出现与我们意愿不一致的结果了,违反了我们重写方法的意愿了。

当然,这种情况是不会出现的,因为 Java 编译器帮我们处理了这种情况。我们来查看 B.class 字节码文件:

// class version 52.0 (52)
// access flags 0x21
// signature LA<Ljava/lang/String;>;
// declaration: B extends A<java.lang.String>
public class B extends A  {
  // compiled from: B.java
  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 4 L0
    ALOAD 0
    INVOKESPECIAL A.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this LB; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public setValue(Ljava/lang/String;)V
   L0
    LINENUMBER 8 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "---B.setValue()---"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L1
    LINENUMBER 9 L1
    RETURN
   L2
    LOCALVARIABLE this LB; L0 L2 0
    LOCALVARIABLE value Ljava/lang/String; L0 L2 1
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x1041
  public synthetic bridge setValue(Ljava/lang/Object;)V
   L0
    LINENUMBER 4 L0
    ALOAD 0
    ALOAD 1
    CHECKCAST java/lang/String
    INVOKEVIRTUAL B.setValue (Ljava/lang/String;)V
    RETURN
   L1
    LOCALVARIABLE this LB; L0 L1 0
    MAXSTACK = 2
    MAXLOCALS = 2
}

我们看到 B 类中有两个 setValue 方法,一个参数为 String 类型,一个参数为 Object 类型,参数为 Object 类型的就是 Java 编译器帮我们生成的桥方法,实际代码如下:

public void setValue(String value){...}
public void setValue(Object value){
    setValue((String)value);
}

桥方法内部其实就是调用了我们自己的 setValue 方法,这样就避免了在重写的时候我们还能调用到父类的方法。

问题还没有完,我们接着看:

public class B extends A<String> {
    @Override
    public String getValue() {
        return super.getValue();
    }
}

B 类重写了 A 类中的 getValue 方法。按照泛型的擦除,父类中的 getValue 方法返回值其实是 Object。

所以其实编译器也帮我们生成了桥方法,这里就不贴字节码文件了,大家可以自己查看。编译后的 B 类其实是这样:

public class B extends A {
    // 自己定义的方法
    public String getValue(){...}
    // 编译器生成的桥方法
    public Object getValue(){
        return getValue();
    }
}

这个时候我们发现 B 类有点颠覆我们的常识了,难道一个类中允许出现方法签名相同的多个方法?

  • 方法签名确实是方法名+参数列表
  • 我们也不能在同一个类中写两个方法签名相同的方法
  • JVM 会用方法名、参数类型和返回类型来确定一个方法,所以针对方法签名相同的两个方法,返回值类型不相同的时候,JVM是能分辨的

当然这种情况不只是在使用泛型的时候会出现,当在重写方法时,指定了一个更加严格的返回值类型,虚拟机会帮我们生成桥方法。该例子中的 A.getValue() 和 B.getValue() 称为具有协变的返回类型。

相关文章

  • Java泛型中的桥方法(Bridge Method)

    前几天在看 Java 泛型的时候,发现了一个有趣的现象。就是在某些情况下,编译器在编译我们的类文件的时候会帮我们自...

  • 3分钟快速搞懂Java的桥接方法

    什么是桥接方法? Java中的桥接方法(Bridge Method)是一种为了实现某些Java语言特性而由编译器自...

  • 3分钟快速搞懂Java的桥接方法

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

  • Java泛型

    参考:Java知识点总结(Java泛型) 自定义泛型类 自定义泛型接口 非泛型类中定义泛型方法 继承泛型类 通配符...

  • JAVA学习进阶 - 你没有见过的JAVA桥方法

    本文主要介绍:编译器处理泛型方法和多态冲突的处理手段,桥方法本文适合对象:想进一步理解JAVA中泛型机制的进阶玩家...

  • java 泛型解析

    Java 泛型 1、泛型的精髓是什么 2、泛型方法如何使用 概述: 泛型在java中具有重要地位,在面向对象编程模...

  • Java泛型:类型擦除

    前情回顾 Java泛型:泛型类、泛型接口和泛型方法 类型擦除 代码片段一 显然在平时使用中,ArrayList (...

  • Java 语法糖及实现

    Java 中的泛型 泛型是什么 泛型,即“参数化类型”,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。 ...

  • Java基础之<泛型>

    泛型类和泛型方法 泛型是Java语言中实现程序多态的一种重要方法,泛型多用于底层代码中,以此来保证代码的通用型。今...

  • 想理解泛型吗?看这一篇就够了!

    一、前言二、泛型类2.1 概述Java中泛型使用情况大致包括三种:泛型类、泛型接口、泛型方法 本节演示泛型类。 2...

网友评论

      本文标题:Java泛型中的桥方法(Bridge Method)

      本文链接:https://www.haomeiwen.com/subject/zupdgqtx.html