it-swarm-ru.tech

Java Дженерики - метод моста?

Нечто, называемое концепцией "метода моста", связанное с Java Обобщением, заставило меня остановиться и задуматься над этим.

Кстати, я знаю только, что это происходит на уровне байт-кода и не доступно для использования.

Но мне не терпится узнать концепцию "метода моста", используемого компилятором Java.

Что именно происходит за кулисами и почему оно используется?

Любая помощь с примером будет принята с благодарностью.

58
sgokhales

Это метод, который позволяет классу, расширяющему универсальный класс или реализующему универсальный интерфейс (с конкретным параметром типа), по-прежнему использоваться в качестве необработанного типа.

Вообразите это:

public class MyComparator implements Comparator<Integer> {
   public int compare(Integer a, Integer b) {
      //
   }
}

Его нельзя использовать в необработанном виде, передавая два Object для сравнения, поскольку типы компилируются в метод сравнения (в отличие от того, что произойдет, если это будет параметр общего типа T, в котором тип будет удален). Поэтому вместо этого компилятор добавляет "метод моста", который выглядит примерно так (если бы он был [Java source):

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);
   }
}

Компилятор защищает доступ к методу моста, следя за тем, чтобы явные обращения непосредственно к нему приводили к ошибке времени компиляции. Теперь класс можно использовать и в необработанном виде:

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;
}

Если метод моста не существует, и вы передали List<Integer> и MyComparator в эту функцию, вызов в строке, помеченной IMPORTANT, завершится неудачно, поскольку у MyComparator не будет метода с именем compare, который принимает два Objects ... только один, который принимает два Integers ,.

FAQ ниже - хорошее чтение.

Смотрите также:

72
Mark Peters

Если вы хотите понять, зачем вам нужен метод моста, вам лучше понять, что происходит без него. Предположим, что нет метода моста.

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), поскольку нет привязки к параметру Type T. В классе B нет метода, подпись которого совпадает с set в A. Так что нет переопределения. Следовательно, когда что-то подобное произошло:

A a=new B();
a.set("Hello World!");

Полиморфизм здесь не сработает. Помните, что вам нужно переопределить метод родительского класса в дочернем классе, чтобы вы могли использовать родительский класс var для запуска полиморфизма.

То, что делает метод моста, - это незаметное переопределение метода в родительском классе всей информацией от метода с тем же именем, но с другой подписью. С помощью метода бриджа полиморфизм сработал. Хотя на поверхности вы переопределяете метод родительского класса методом другой сигнатуры.

2
ch48h2o

Интересно отметить, что компилятор выводит метод MyComparator:

public int compare(Integer a, Integer b) {/* code */}

пытается переопределить Comparator<T>

public int compare(T a, T b);

из объявленного типа Comparator<Integer>. В противном случае MyComparator's compare будет рассматриваться компилятором как дополнительный (перегрузочный), а не переопределенный метод. И как таковой, не было бы никакого метода моста, созданного для этого.

0
Callistus