Java Generics - метод моста?
Что-то под названием "метод моста", связанное с Java Generics, заставило меня остановиться и задуматься над этим.
Кстати, я знаю только, что это происходит на уровне байт-кода и недоступно для использования.
Но мне не терпится узнать концепцию "метода моста", используемого компилятором Java.
Что именно происходит за кулисами и почему оно используется?
Любая помощь с примером будет принята с благодарностью.
3 ответа
Это метод, который позволяет классу, расширяющему универсальный класс или реализующему универсальный интерфейс (с конкретным параметром типа), по-прежнему использоваться в качестве необработанного типа.
Вообразите это:
public class MyComparator implements Comparator<Integer> {
public int compare(Integer a, Integer b) {
//
}
}
Это не может быть использовано в необработанном виде, передавая два Object
s для сравнения, потому что типы компилируются в метод сравнения (вопреки тому, что произошло бы, если бы это был параметр общего типа T, где тип был бы удален). Поэтому вместо этого компилятор добавляет "метод моста", который выглядит примерно так (если бы это был источник Java):
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
это занимает два Object
s... только один, который занимает два Integer
s.
Ниже приведены часто задаваемые вопросы.
Смотрите также:
Если вы хотите понять, зачем вам нужен метод моста, вам лучше понять, что происходит без него. Предположим, что нет метода моста.
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
в А стало public void set(Object newVal)
поскольку нет ограничения на параметр типа T. В классе B нет метода, сигнатура которого совпадает с set
в А. Так что нет переопределения. Следовательно, когда что-то подобное произошло:
A a=new B();
a.set("Hello World!");
Полиморфизм здесь не сработает. Помните, что вам нужно переопределить метод родительского класса в дочернем классе, чтобы вы могли использовать родительский класс var для запуска полиморфизма.
То, что делает метод моста, - это незаметное переопределение метода в родительском классе всей информацией от метода с тем же именем, но с другой подписью. С помощью бридж-метода сработал полиморфизм. Хотя на поверхности вы переопределяете метод родительского класса методом другой сигнатуры.
Интересно отметить, что компилятор делает вывод, что MyComparator
метод:
public int compare(Integer a, Integer b) {/* code */}
пытается переопределить Comparator<T>
"s
public int compare(T a, T b);
от заявленного типа Comparator<Integer>
, Иначе, MyComparator
"s compare
будет рассматриваться компилятором как дополнительный (перегрузочный), а не переопределенный метод. И как таковой, не было бы никакого метода моста, созданного для этого.
Как указано в этой статье и в этой статье, ключевой причиной использования метода моста Java является стирание типов и полиморфизм.
В качестве примера возьмем класс ArrayDeque (исходный код), он содержитclone()
как показано ниже, потому что класс ArrayDeque
реализует Cloneable
интерфейс, поэтому он должен переопределить Object.clone()
метод.
public class ArrayDeque<E> extends AbstractCollection<E>
implements Deque<E>, Cloneable, Serializable
{
public ArrayDeque<E> clone() {
....
}
}
Но проблема в возвращаемом типе ArrayDeque.clone()
является ArrayDeque<E>
, и он не соответствовал сигнатуре метода, определенной в родительском Object.clone()
, а в Object.java тип возвращаемого значения -Object
вместо.
public class Object {
protected native Object clone() throws CloneNotSupportedException;
}
Несоответствие возвращаемого типа является проблемой для полиморфизма. Итак, в скомпилированном файле результатовArrayDeque.class
, компилятор Java сгенерировал два clone()
методы, один соответствует подписи в исходном коде, другой соответствует подписи в родительском классе Object.clone()
.
- clone() метод возвращает
ArrayDeque<E>
, который генерируется на основе соответствующего исходного кода - clone() метод возвращает
Object
, который генерируется на основеObject.clone()
. Этот метод ничего не делает, кроме вызова другогоclone()
метод. И этот метод помечен как ACC_BRIDGE, что указывает на то, что этот метод сгенерирован компилятором для целей моста.