Переопределение метода с универсальным типом возврата завершается неудачно после добавления параметра - PullRequest
0 голосов
/ 11 мая 2018

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

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) подписи mA.
  • ...

Подписи определены в JLS §8.4.2 :

Два метода или конструктора, M и N, имеют одинаковую сигнатуру , если они имеют одинаковое имя, одинаковые параметры типа (если есть) (§8.4.4) и после адаптации формального параметры типа N для параметров типа M, те же формальные параметры типа.

Подпись метода m1 является подписью метода m2, если либо:

  • м2 имеет ту же подпись, что и m1, или
  • подпись m1 совпадает с удалением (§4.6) подписи m2.

Похоже, что B.getSupplier является подписью A.getSupplier, но B.getSuppliers является , а не подписью A.getSuppliers.

Интересно, как это может быть.

Если B.getSupplier является подписью A.getSupplier, поскольку оно имеет такое же стирание, то B.getSuppliers также должно иметь такое же стирание, что и A.getSuppliers. Этого должно быть достаточно, чтобы переопределение getSuppliers было законным, но это не так.

Если B.getSupplier является подписью A.getSupplier, поскольку она имеет одинаковую подпись, то мне интересно, что именно означает "параметры одного типа (если есть)".

Если рассматриваются параметры типа, то они должны иметь разные параметры типа: A.getSupplier имеет параметр типа X, B.getSupplier не имеет ни одного.
Если параметры типа не учитываются, тогда чем же отличается getSuppliers? 1069 *

Это скорее академический вопрос о переопределениях и обобщениях, поэтому, пожалуйста, не предлагайте рефакторинг кода (например, перенос параметра типа X в класс и т. Д.).

Я ищу официальный ответ на основе JLS.

С моей точки зрения, B.getSupplier не должно быть в состоянии переопределить A.getSupplier, так как они не имеют одинаковые параметры типа. Это делает следующий код (который выдает ClassCastException) легальным:

A b = new B();
URL url = b.<URL>getSupplier().get();

Ответы [ 2 ]

0 голосов
/ 12 мая 2018

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

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

0 голосов
/ 12 мая 2018

Согласно выводу компилятора, сигнатуры метода в обоих примерах различны (скомпилируйте код с опцией -Xlint:unchecked, чтобы подтвердить это):

<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 *)1046 *), потому что:

  • подпись m 1 совпадает с удалением подписи m 2

<X>getSupplier() после стирания равно getSupplier().

Неправильное переопределение

B.getSuppliers(...)1 ) неподпись A.<X>getSuppliers(...)2 ), потому что:

  • подпись m 1 отличается от стиранияподпись м 2

подпись м 1 :

getSuppliers(Collection<String> strings);

стирание подписи м 2 :

getSuppliers(Collection strings);

Изменение аргумента m 1 с Collection<String> на необработанный Collection устраняет ошибку, в данном случае m 1 становится подписью m 2 .

Заключение

1-й фрагмент кода (допустимое переопределение) : сигнатуры методав родительском и дочернем классах разные изначально.Но после применения стирания к родительскому методу подписи становятся одинаковыми.

2-й фрагмент кода (недопустимое переопределение) : сигнатуры метода изначально различны и остаются разными после применения стирания кродительский метод.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...