2

假设我有这个通用类:

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 类做一些例子吗?

我已经阅读了其他答案,但是如果没有具体的例子,我仍然无法完全理解。

4

1 回答 1

4

你几乎猜对了。几乎,因为桥接方法桥接方法调用并且不重复方法实现。您的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 不考虑泛型类型,它将所有出现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变量是否包含IntItemItem(或任何其他兼容类)的实例。因此,不能保证带有字节码签名的方法是否set(Integer): void存在。这就是 Java 编译器应用类型擦除并将Item<Integer>其视为原始类型的原因。通过查看原始类型,set(Object): void调用在Item其自身上定义并因此始终存在的方法。

因此,IntItem该类无法确定其方法是使用擦除类型(它继承自Item)的方法还是使用显式类型的方法调用的。因此,桥接方法被隐式实现以创建单一版本的真值。通过动态调度桥,你可以set(Integer)在一个子类中重写,IntItem并且桥方法仍然有效。

桥接方法也可用于实现方法的协变返回类型。这个特性是在引入泛型时添加的,因为桥接方法已经作为一个概念存在。这个功能是免费的,可以这么说。桥接方法还有第三种应用,它们实现了可见性桥接。这是克服反射引擎的访问限制所必需的。

于 2014-07-08T08:47:29.273 回答