Методы моста в обобщениях Java. Является ли этот пример правильным?

Допустим, у меня есть этот общий класс:

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?

Я уже прочитал другие ответы, но я все еще не получаю это полностью без конкретного примера.

1 ответ

Решение

Вы почти поняли это правильно. Почти, потому что методы моста соединяют вызовы методов и не дублируют реализации методов. Ваш 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 переменная содержит экземпляр IntItem или из Item (или любой другой совместимый класс). Таким образом, нет гарантии, что метод с сигнатурой байтового кода set(Integer): void даже существует. Вот почему компилятор Java применяет стирание типа и смотрит на Item<Integer> как будто это был сырой тип. Глядя на необработанный тип, метод set(Object): void вызывается, который определен на Item сам и потому всегда существует.

Как следствие, IntItem класс не может быть уверен, что его методы вызываются с использованием метода с удаленным типом (который он наследует от Item) или методы с явным типом. Поэтому методы моста реализованы неявно для создания единой версии истины. Динамически распределяя мост, вы можете переопределить set(Integer) в подклассе IntItem и методы моста все еще работают.

Методы моста также могут использоваться для реализации ковариантного возвращаемого типа метода. Эта функция была добавлена, когда были представлены дженерики, потому что методы моста уже существовали в качестве концепции. Эта функция пришла бесплатно, так сказать. Существует третье применение методов моста, где они реализуют мост видимости. Это необходимо для преодоления ограничения доступа механизма отражения.

Другие вопросы по тегам