Java泛型-桥接方法?


67

与Java泛型相关的所谓“桥接方法”概念使我停下来思考一下。

顺便说一句,我只知道它发生在字节码级别,因此我们无法使用。

但是我很想知道Java编译器使用的“桥接方法”背后的概念。

幕后到底发生了什么,为什么要使用它?

一个例子的任何帮助将不胜感激。


14
我无法比这更清楚地解释它:stas-blogspot.blogspot.com/2010/03/…(碰巧是Google的第一个结果)
Bozho

Answers:


87

这种方法允许类扩展通用类或实现通用接口(带有具体的类型参数)以仍然用作原始类型。

想象一下:

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

不能以原始形式使用它,传递两个Objects进行比较,因为类型被编译到compare方法中(与之相反,如果它是通用类型参数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);
   }
}

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

Object a = 5;
Object b = 6;

Comparator rawComp = new MyComparator();
int comp = rawComp.compare(a, b);

为什么还需要它?

除了增加对原始类型的显式使用(主要是为了向后兼容)的支持之外,还需要桥接方法来支持类型擦除。使用类型擦除,方法如下:

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;
}

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

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;
}

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

以下常见问题是一本好书。

也可以看看:


5
即使在没有泛型的情况下,也适用于协变返回类型。
汤姆·哈特芬

2
@paulmurray:什么是“等于(T o)”?
user102008 2012年

6
@gstackoverflow假设您有一个方法Object get(),然后在子类中使用覆盖了它String get()。JVM将仅覆盖包括返回类型在内的完全匹配的签名。所以javac的创建一个合成桥法Object get()(又名get()Ljava/lang/Object;)与执行的呼叫String get()(又名get()Ljava/lang/String;)。从1.5版开始,这只能在源代码中实现,并且您不能定位比源代码更早版本的字节码。
Tom Hawtin-大头钉

3
@AndrewTobilko:是的,据报道是公开的javap -v MyComparator.class。但是,正如我提到的,编译器阻止您直接在参数化类型上调用bridge方法,因此我会说它是“有警告的公共”。如果看一下的输出javap,您将看到bridge方法尽管有ACC_PUBLIC,但还有两个附加标志:ACC_BRIDGEACC_SYNTHETIC。这就是编译器将用来禁止访问的内容。
马克·彼得斯

3
@AndrewTobilko:还请注意,这并不意味着所有桥接方法都是公共的,只是我的示例中的一种。如果要使用受保护的泛型方法扩展类(并为其提供具体类型),并使用受保护的方法覆盖它,则将对bridge方法进行保护以使其匹配。
马克·彼得斯

5

如果您想了解为什么需要桥接方法,则最好了解没有它的情况。假设没有桥接方法。

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);
  }
}

请注意,擦除后,法setA成为public void set(Object newVal)因为是在参数类型没有约束T。有类没有方法B的签名,其中相同setA。因此没有覆盖。因此,当发生这种情况时:

A a=new B();
a.set("Hello World!");

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

桥方法的作用是用名称相同但签名不同的方法的所有信息以静默方式覆盖父类中的方法。借助于桥接方法,多态性起作用了。尽管从表面上看,您可以使用具有不同签名的方法覆盖父类方法。


我检查了整个互联网,但发现您的答案很简单而且很明确。感谢百万次。
堆栈溢出

@SunnyKhan当我第一次看到桥接方法时,就是我!幸运的是,我知道了它是如何工作的!
ch48h2o

0

令人惊讶的是,编译器会推断出MyComparator方法:

public int compare(Integer a, Integer b) {/* code */}

正在尝试覆盖Comparator<T>

public int compare(T a, T b);

从声明的类型Comparator<Integer>。否则,编译器会将MyComparatorscompare作为附加(重载)方法,而不是覆盖该方法。因此,将不会为此创建桥方法。


0

正如本文本文所指出的,Java桥接方法的主要原因是类型擦除多态性

让我们以ArrayDeque类(源代码)为例,它包含一个clone()如下所示的方法,因为该类ArrayDeque实现了Cloneable接口,因此它必须重写该Object.clone()方法。

public class ArrayDeque<E> extends AbstractCollection<E>
                        implements Deque<E>, Cloneable, Serializable
{

  public ArrayDeque<E> clone() {
    ....
  }
}

ArrayDeque的UML层次图

但问题是返回类型ArrayDeque.clone()就是ArrayDeque<E>,它没有匹配到父定义的方法签名Object.clone(),并在Object.java返回类型为Object代替。

public class Object {

    protected native Object clone() throws CloneNotSupportedException;
}

返回类型不匹配是多态性的问题。因此,在编译结果文件中ArrayDeque.class,Java编译器生成了两种clone()方法,一种与源代码中的签名匹配,另一种与父类中的签名匹配Object.clone()

  1. clone()方法return ArrayDeque<E>,这是根据相应的源代码生成的
  2. 返回的clone()方法Object基于生成Object.clone()该方法除了调用另一个clone()方法外什么也不做。并且,此方法被标记为ACC_BRIDGE,指示此方法是由编译器出于桥接目的生成的。

基于ArrayDeque.class生成的UML图

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.