桥方法 Java 泛型? ?

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

顺便说一下,我只知道它发生在 字节码级别,并且不可用 供我们使用。

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

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

如果您能提供示例帮助,我将不胜感激。

21684 次浏览

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

想象一下:

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

这不能以原始形式使用,传递两个 Object进行比较,因为类型被编译到比较方法中(与泛型类型参数 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);
}
}

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

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

如果桥方法不存在,并且您向这个函数传递了一个 List<Integer>和一个 MyComparator,那么标记为 IMPORTANT的行上的调用将会失败,因为 MyComparator将没有一个取两个 Object的方法称为 compare... 只有一个取两个 Integer

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

参见:

有趣的是,编译器 推论MyComparator方法:

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

正试图覆盖 Comparator<T>

public int compare(T a, T b);

从声明的类型 Comparator<Integer>。否则,编译器将把 MyComparatorcompare作为一个附加(重载)方法而不是重写方法。因此,不会为它创建桥接方法。

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

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

注意,在擦除之后,A中的方法 set变成了 public void set(Object newVal),因为对 Type 参数 T没有限制。在 B类中没有方法的签名与 A中的 set相同。所以没有超驰装置。因此,当这样的事情发生时:

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

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

桥方法所做的是使用来自具有相同名称但签名不同的方法的所有信息悄悄地重写父类中的方法。在桥方法的帮助下,多态性起作用了。虽然表面上,您使用不同签名的方法重写父类方法。

这篇文章这篇文章所示,Java 桥方法的关键原因是 类型删除多态性

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

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


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

UML Hierarchy Diagram of ArrayDeque

但问题是 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 () 方法返回基于相应源代码生成的 ArrayDeque<E>
  2. Clone () 方法返回基于 Object.clone()生成的 Object。而且,这个方法被标记为 ACC _ Bridge,这表明这个方法是由编译器为桥生成的。

UML diagram generated based on ArrayDeque.class