Переопределение метода с типичным типом возврата не выполняется после добавления параметра
Интересно, почему это допустимое переопределение:
public abstract class A {
public abstract <X> Supplier<X> getSupplier();
public static class B extends A {
@Override
public Supplier<String> getSupplier() {
return String::new;
}
}
}
В то время как это не так:
public abstract class A {
public abstract <X> Supplier<X> getSuppliers(Collection<String> strings);
public static class B extends A {
@Override
public Supplier<String> getSuppliers(Collection<String> strings) {
return String::new;
}
}
}
Согласно JLS §8.4.8.1, B.getSupplier
должен быть подъявлением A.getSupplier
:
Метод экземпляра mC, объявленный или унаследованный классом C, переопределяет из C другой метод mA, объявленный в классе A, если все это верно:
- ...
- Подпись mC является подзаконной (§8.4.2) сигнатуры мА.
- ...
Подписи определены в JLS §8.4.2:
Два метода или конструкторы M и N имеют одну и ту же подпись, если они имеют одинаковое имя, те же параметры типа (если они есть) (§8.4.4) и после адаптации формальных типов параметров N к параметрам типа из M, те же формальные типы параметров.
Подпись метода m1 является подширкой сигнатуры метода m2, если либо:
- m2 имеет ту же подпись, что и m1, или
- подпись m1 такая же, как стирание (§4.6) подписи m2.
Таким образом, похоже, что B.getSupplier
- подглавность A.getSupplier
но B.getSuppliers
не является подъявлением A.getSuppliers
.
Интересно, как это может быть.
Если B.getSupplier
является подъявлением A.getSupplier
поскольку он имеет такое же стирание, то B.getSuppliers
также должен иметь такое же стирание, как A.getSuppliers
. Этого должно быть достаточно для getSuppliers
чтобы переопределить getSuppliers
чтобы быть законным, но это не так.
Если B.getSupplier
является подъявлением A.getSupplier
потому что он имеет одну и ту же подпись, тогда я задаюсь вопросом, что именно означает "параметры одного и того же типа (если они есть)".
Если параметры типа считаются, то они должны иметь разные параметры типа: A.getSupplier
имеет параметр типа X
, B.getSupplier
имеет.
Если параметры типа не учитываются, то как getSuppliers
отличаются?
Это скорее академический вопрос об переопределениях и дженериках, поэтому, пожалуйста, не предлагайте рефакторинг кода (например, перемещение параметра типа X
в класс и т.д.).
Я ищу официальный, основанный на JLS ответ.
С моей точки зрения, B.getSupplier
не должен переопределять A.getSupplier
поскольку они не имеют одинаковых параметров типа. Это делает следующий код (который производит ClassCastException
) законным:
A b = new B();
URL url = b.<URL>getSupplier().get();
Ответы
Ответ 1
Согласно -Xlint:unchecked
компилятора, в обоих примерах сигнатуры методов различны (скомпилируйте код с -Xlint:unchecked
option, чтобы подтвердить его):
<X>getSupplier() in A (m2)
1st snippet
getSupplier() in B (m1)
<X>getSuppliers(Collection<String> strings) in A (m2)
2nd snippet
getSuppliers(Collection<String> strings) in B (m1)
Согласно спецификации JLS, сигнатура метода m 1 является подсигналом сигнатуры метода m 2, если:
-
m 2 имеет ту же подпись, что и m 1, или
-
подпись m 1 такая же, как стирание сигнатуры m 2.
Первое утверждение вне игры - подписи методов различны. Но как насчет второго заявления и стирания?
Действительное переопределение
B.getSupplier()
(m 1) является подъявлением A.<X>getSupplier()
(m 2), потому что:
- подпись m 1 такая же, как стирание сигнатуры m 2
<X>getSupplier()
после стирания равно getSupplier()
.
Недопустимое переопределение
B.getSuppliers(...)
(m 1) не является подъявлением A.<X>getSuppliers(...)
(m 2), потому что:
- подпись m 1 не такая, как стирание сигнатуры m 2
Подпись m 1:
getSuppliers(Collection<String> strings);
Стирание сигнатуры m 2:
getSuppliers(Collection strings);
Изменение аргумента m 1 из Collection<String>
в исходную Collection
устраняет ошибку, в этом случае m 1 становится поднаклейкой m 2.
Заключение
1-й фрагмент кода (действительное переопределение): сигнатуры метода в родительском и дочернем классах отличаются изначально. Но после применения стирания к родительскому методу подписи становятся одинаковыми.
2-й фрагмент кода (недопустимое переопределение): сигнатуры метода отличаются первоначально и остаются разными после применения стирания к родительскому методу.
Ответ 2
В тот момент, когда вы добавили параметр, он перестали быть переопределением и стал перегрузкой.
Дженерики не имеют к этому никакого отношения.