假设我有这个通用类:
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>();
这两个实例共享相同的原始类:
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 类做一些例子吗?
我已经阅读了其他答案,但如果没有具体的例子,我仍然无法完全理解。
最佳答案
你几乎猜对了。几乎,因为桥接方法桥接方法调用并且不重复方法实现。您的IntItem
类看起来像以下脱糖版本(您可以使用例如 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 不会考虑泛型类型,因为 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
的子类中并且桥接方法仍然有效。
桥接方法还可用于实现方法的协变返回类型。该功能是在引入泛型时添加的,因为桥接方法已经作为概念存在。可以说,这个功能是免费的。桥接方法还有第三种应用,即 implement a visibility bridge 。这对于克服反射引擎的访问限制是必要的。
关于java - Java 泛型中的桥接方法。这个例子正确吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24625612/