Переопределение общего метода с помощью общего в Java
Анжелика Лангер говорит в своем FAQ о дженериках (см. Technicalities.FAQ822):
Если методы имеют параметры типа с разными границами, то они не переопределяйте, потому что методы имеют подписи, которые не являются переопределить-эквивалент. Помните, что границы параметров типа являются частью общая сигнатура метода.
Пример (общих методов подтипа, перегружающих общий супертип методы; не рекомендуется):
class Super {
public <T> void set( T arg) { ... }
public <T> T get() { ... }
}
class Sub extends Super {
public <S extends Number > void set( S arg) { ... } // overloads
public <S extends Number > S get() { ... } // overloads
}
Я не понимаю, почему метод get
перегружен в классе Sub
. Для того, что я знаю, это должна быть ошибка времени компиляции, потому что get
имеет ту же подпись как в Sub
, так и Super
(тип возврата не является частью этого).
Что еще меня путает, так это то, что IDE, который я использую для тестирования кода (IntelliJ IDEA 14.0.3), выделяет get
в Sub
как ошибку компиляции со следующим сообщением:
'get()' в 'Sub' столкновения с 'get()' в 'Super'; оба метода имеют одно и то же стирание, но не переопределяют другой.
Но когда я запускаю программу, она компилируется и выполняется без проблем. Я полагаю, что есть некоторая ошибка в IntelliJ, когда она анализирует код, и что правильно, что говорит Анжелика в своем FAQ. Но я не могу уловить смысл.
Ответы
Ответ 1
Согласно JLS, подпись метода не включает тип возвращаемого значения, а только имя метода и тип его параметров. Это означает, что при компиляции Super и Sub ошибка компиляции должна возвращаться, поскольку Sub.get() имеет то же стирание, что и Super.get(), но не переопределяет и не перегружает Super.get(). Он не может переопределить, потому что ограниченный тип X extends Number не является подтипом типа X, и он не может перегружать, потому что тип возврата не является частью сигнатуры метода. Sub.set перегружает Super.set в этом случае.
Что вы можете скомпилировать и запустить. Если вы используете Java 6, в Java 6 есть известный bug, который бы скомпилировал Super и Sub. В Java 7 это исправлено и не будет разрешено.
Ответ 2
Я думаю, что стирание public <S extends Number> S get()
не такое же или ковариантное, что и у public <T> T get()
.
Это компилируется и запускается:
class B {
public <T> T get() {
return null;
}
}
class A extends B {
@Override
public <S> S get() {
return null;
}
Это не означает:
class B {
public <T> T get() {
return null;
}
}
class A extends B {
@Override
public <S extends Number> S get() {
return null;
}
Ответ 3
Из того, что я понимаю, почему это перегрузка вместо переопределения - это потому, что компилятор не считает это переопределяющим эквивалентом. Далее в FAQ Angelika Langer вы видите, что он обсуждает, почему переопределение метода не так, как ожидалось.
http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ051
Подводя итог, методы имеют одинаковые имена и параметры, но на основе общего подтипа определяются как разные. Даже с аннотацией переопределения компилятор рассматривает это как перегруженный метод. При использовании дженериков добавлен слой для метода, где общий тип применяется как часть метода, который может однозначно определить его.