Подтвердить что ты не робот

Переопределение метода с типичным типом возврата не выполняется после добавления параметра

Интересно, почему это допустимое переопределение:

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();
4b9b3361

Ответ 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

В тот момент, когда вы добавили параметр, он перестали быть переопределением и стал перегрузкой.

Дженерики не имеют к этому никакого отношения.