与Java泛型相关的所谓“桥接方法”概念使我停下来思考一下。
顺便说一句,我只知道它发生在字节码级别,因此我们无法使用。
但是我很想知道Java编译器使用的“桥接方法”背后的概念。
幕后到底发生了什么,为什么要使用它?
一个例子的任何帮助将不胜感激。
Answers:
这种方法允许类扩展通用类或实现通用接口(带有具体的类型参数)以仍然用作原始类型。
想象一下:
public class MyComparator implements Comparator<Integer> {
public int compare(Integer a, Integer b) {
//
}
}
不能以原始形式使用它,传递两个Object
s进行比较,因为类型被编译到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
需要两个Object
s ...只有一个方法需要两个Integer
s。
以下常见问题是一本好书。
Object get()
,然后在子类中使用覆盖了它String get()
。JVM将仅覆盖包括返回类型在内的完全匹配的签名。所以javac的创建一个合成桥法Object get()
(又名get()Ljava/lang/Object;
)与执行的呼叫String get()
(又名get()Ljava/lang/String;
)。从1.5版开始,这只能在源代码中实现,并且您不能定位比源代码更早版本的字节码。
javap -v MyComparator.class
。但是,正如我提到的,编译器阻止您直接在参数化类型上调用bridge方法,因此我会说它是“有警告的公共”。如果看一下的输出javap
,您将看到bridge方法尽管有ACC_PUBLIC
,但还有两个附加标志:ACC_BRIDGE
和ACC_SYNTHETIC
。这就是编译器将用来禁止访问的内容。
如果您想了解为什么需要桥接方法,则最好了解没有它的情况。假设没有桥接方法。
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);
}
}
请注意,擦除后,法set
中A
成为public void set(Object newVal)
因为是在参数类型没有约束T
。有类没有方法B
的签名,其中相同set
的A
。因此没有覆盖。因此,当发生这种情况时:
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() {
....
}
}
但问题是返回类型ArrayDeque.clone()
就是ArrayDeque<E>
,它没有匹配到父定义的方法签名Object.clone()
,并在Object.java返回类型为Object
代替。
public class Object {
protected native Object clone() throws CloneNotSupportedException;
}
返回类型不匹配是多态性的问题。因此,在编译结果文件中ArrayDeque.class
,Java编译器生成了两种clone()
方法,一种与源代码中的签名匹配,另一种与父类中的签名匹配Object.clone()
。
ArrayDeque<E>
,这是根据相应的源代码生成的Object
基于生成Object.clone()
。该方法除了调用另一个clone()
方法外什么也不做。并且,此方法被标记为ACC_BRIDGE,指示此方法是由编译器出于桥接目的生成的。