Java Generics - Bridge方法?

sgo*_*les 62 java generics

与Java Generics相关的称为"桥接方法"概念的东西让我停下来思考它.

顺便说一句,我只知道它出现在字节码级别,我们无法使用.

但我很想知道Java编译器使用的"桥接方法"背后的概念.

幕后究竟发生了什么以及为何使用它?

任何有关示例的帮助将不胜感激.

Mar*_*ers 80

它是一种允许扩展泛型类或实现泛型接口(具有具体类型参数)的类仍然可以用作原始类型的方法.

想象一下:

public class MyComparator implements Comparator<Integer> {
   public int compare(Integer a, Integer b) {
      //
   }
}
Run Code Online (Sandbox Code Playgroud)

这不能以其原始形式使用,传递两个Objects进行比较,因为类型被编译到比较方法(与通用类型参数T,其中类型将被擦除的情况相反).因此,在幕后,编译器添加了一个"桥接方法",看起来像这样(它是Java源代码):

public class MyComparator implements Comparator<Integer> {
   public int compare(Integer a, Integer b) {
      //
   }

   //THIS is a "bridge method"
   public int compare(Object a, Object b) {
      return compare((Integer)a, (Integer)b);
   }
}
Run Code Online (Sandbox Code Playgroud)

编译器保护对bridge方法的访问,强制直接对其进行显式调用会导致编译时错误.现在这个类也可以以原始形式使用:

Object a = 5;
Object b = 6;

Comparator rawComp = new MyComparator();
int comp = rawComp.compare(a, b);
Run Code Online (Sandbox Code Playgroud)

为什么还需要它?

除了添加对原始类型的显式使用的支持(主要用于向后兼容性)之外,还需要桥接方法来支持类型擦除.使用类型擦除,这样的方法:

public <T> T max(List<T> list, Comparator<T> comp) {
   T biggestSoFar = list.get(0);
   for ( T t : list ) {
       if (comp.compare(t, biggestSoFar) > 0) {
          biggestSoFar = t;
       }
   }
   return biggestSoFar;
}
Run Code Online (Sandbox Code Playgroud)

实际上编译成与此兼容的字节码:

public Object max(List list, Comparator comp) {
   Object biggestSoFar = list.get(0);
   for ( Object  t : list ) {
       if (comp.compare(t, biggestSoFar) > 0) {  //IMPORTANT
          biggestSoFar = t;
       }
   }
   return biggestSoFar;
}
Run Code Online (Sandbox Code Playgroud)

如果bridge方法不存在并且您将a List<Integer>和a 传递MyComparator给此函数,则标记的行的调用IMPORTANT将失败,因为MyComparator没有调用的方法compare需要两个Objects ...只有一个需要两个Integers.

下面的常见问题是一个很好的阅读.

也可以看看:

  • 即使在没有泛型的情况下,也适用于协变返回类型. (5认同)
  • @gstackoverflow假设你有一个方法`Object get()`然后在一个子类中覆盖它,比如`String get()`.JVM将仅覆盖完全匹配的签名,包括返回类型.所以javac用一个实现调用`String get()`(又叫`get()Ljava/lang/String; `).这在源代码中只能从1.5开始实现,并且您无法定位比源代码更早的字节码版本. (5认同)
  • @AndrewTobilko:是的,它是公开的,如`javap -v MyComparator.class`所报告的.但是,正如我所提到的,编译器阻止你直接在参数化类型上调用bridge方法,所以我会说它是"公开的,但需要注意".如果你看一下`javap`的输出,你会发现尽管有'ACC_PUBLIC`,但是桥方法有两个额外的标志:`ACC_BRIDGE`和`ACC_SYNTHETIC`.这是编译器将用于禁止访问的内容. (3认同)
  • @AndrewTobilko:请注意,这并不意味着所有桥接方法都是公共的,只是我的示例中的方法.如果您使用受保护的泛型方法扩展类(并赋予其具体类型),并使用受保护的方法覆盖它,则将保护桥接方法以匹配. (3认同)
  • @paulmurray:什么是"等于(T o)"? (2认同)

小智 6

如果您想了解为什么需要桥接方法,您最好了解没有它会发生什么。假设没有桥接方法。

class A<T>{
  private T value;
  public void set(T newVal){
    value=newVal
  }
}

class B extends A<String>{
  public void set(String newVal){
    System.out.println(newVal);
    super.set(newVal);
  }
}
Run Code Online (Sandbox Code Playgroud)

请注意,擦除后,方法setinA变为了,public void set(Object newVal)因为 Type parameter 没有限制T。有类没有方法B的签名,其中相同setA。所以没有覆盖。因此,当这样的事情发生时:

A a=new B();
a.set("Hello World!");
Run Code Online (Sandbox Code Playgroud)

多态在这里不起作用。请记住,您需要在子类中覆盖父类的方法,以便您可以使用父类 var 来触发多态。

桥接方法的作用是使用来自同名但签名不同的方法的所有信息静默覆盖父类中的方法。在桥接方法的帮助下,多态起作用了。尽管从表面上看,您使用不同签名的方法覆盖了父类方法。