我们说我有这个通用类:
class Item<T> {
private T item;
public void set(T item) {
this.item = item;
}
public T get() {
return item;
}
}
如果我创建这样的2个实例:
Item<Integer> intItem = new Item<Integer>();
Item<String> stringItem = new Item<String>();
2个实例共享同一个原始类:
class Item {
private Object item;
public void set(Object item) {
this.item = item;
}
public Object get() {
return item;
}
}
现在,如果我像这样扩展类Item:
class IntItem extends Item<Integer>{
private Integer item;
public void set(Integer item) {
this.item = item;
}
public Integer get() {
return item;
}
}
创建了这些桥接方法:
class IntItem extends Item<Integer>{
private Integer item;
//Bridge method 1
public void set(Object item) {
this.item = (Integer) item;
}
public void set(Integer item) {
this.item = item;
}
//Bridge method 2
public Object get() {
return item;
}
public Integer get() {
return item;
}
}
我到现在为止做得对吗? 我的问题是,为什么以及何时需要桥接方法? 你能用这个Item类做一些例子吗?
我已经阅读了其他答案,但如果没有具体的例子,我仍然无法完全理解。
答案 0 :(得分:3)
你差不多了。几乎是因为桥接方法桥接方法调用而不重复方法实现。您的IntItem
类看起来像下面的desugared版本(您可以使用例如javap
验证这一点):
class IntItem extends Item<Integer> {
private Integer item;
// Bridge method 1
public void set(Object item) {
set((Integer) item);
}
public void set(Integer item) {
this.item = item;
}
//Bridge method 2
public Object get() {
return <Integer>get(); // pseudosyntax
}
public Integer get() {
return item;
}
}
在Java字节代码中,允许定义两个仅根据返回类型不同的方法。这就是为什么有两个方法get
无法使用Java语言显式定义的原因。事实上,您需要在字节代码格式中的任何方法调用上命名参数类型和返回类型。
这就是为什么你首先需要桥接方法。 Java编译器对泛型类型应用类型擦除。这意味着,JVM不会考虑泛型类型,它将Item<Integer>
的所有出现视为原始Item
。但是,这对于类型的显式命名不起作用。最后,ItemInt
本身不再具有通用性,因为它覆盖了具有显式类型版本的所有方法,这些版本对于具有这些显式类型的JVM是可见的。因此,IntItem
在其加糖版本中甚至不会覆盖Item
的任何方法,因为签名不兼容。为了使通用类型对JVM透明,Java编译器需要插入这些桥接方法,这些方法事实上覆盖原始实现,以便桥接对IntItem
中定义的方法的调用。这样,您间接覆盖方法并获得您期望的行为。请考虑以下情形:
IntItem nonGeneric = new IntItem();
nonGeneric.set(42);
如上所述,IntItem::set(Integer)
方法不是通用的,因为它被非通用类型的方法覆盖。因此,调用此方法不涉及类型擦除。 Java编译器只是编译上面的方法调用,以使用字节代码签名set(Integer): void
调用该方法。就像你期望的那样。
但是,在查看以下代码时:
Item<Integer> generic = ...;
generic.set(42);
编译器无法确定generic
变量是否包含IntItem
或Item
(或任何其他兼容类)的实例。因此,不能保证具有字节代码签名set(Integer): void
的方法甚至存在。这就是Java编译器应用类型擦除并查看Item<Integer>
的原因,就像它是原始类型一样。通过查看原始类型,调用方法set(Object): void
,该方法在Item
本身上定义,因此始终存在。
因此,IntItem
类无法确定是否使用具有已擦除类型的方法(它继承自Item
)或具有显式类型的方法调用其方法。因此,隐式地实现桥接方法以创建单个版本的真值。通过动态分派桥,您可以覆盖set(Integer)
的子类中的IntItem
,并且桥接方法仍然有效。
Bridge方法也可用于实现方法的协变返回类型。引入泛型时添加了此功能,因为桥接方法已作为概念存在。这个功能免费提供,可以这么说。桥梁方法的第三种应用是implement a visibility bridge。这对于克服反射引擎的访问限制是必要的。