Почему List <String>не может быть одновременно аргументом метода generi c базового класса и метода non-generi c производного класса? - PullRequest
4 голосов
/ 05 апреля 2020

Почему List<String> не может быть одновременно аргументом метода generi c базового класса и метода non-generi c класса Derived?

 class Base {
     <T> void f(List<String> arg) {}
 }

 class Derived extends Base {
     void f(List<String> arg) {}

     // above is compile ERROR: method f(List<String>) of type Derived has the same erasure 
     //as f(List<String>) of type Base but does not override it
 }

Я не понимаю этого сообщение компилятора и причина ошибки компиляции.

Нет проблем с типом возврата:

class Base {
     <T> List<String> f() { return null; }
}

class Derived extends Base {     
    List<String> f() { return null; }  // perfectly valid as return-type
}

Ответы [ 2 ]

4 голосов
/ 11 апреля 2020

Ух ты, это был забавный поиск.

Это зависит от точной спецификации, когда говорят, что метод переопределяет другой метод.

Соответствующая часть находится в §8.4.8.1 Переопределение (методами экземпляра) Java Спецификация языка (я использую один для Java 14).

Метод экземпляра mC, объявленный или унаследованный классом C, переопределяет C другой метод mA, объявленный в классе A, если все следующее верно:

[...]

  • Подпись m C является подписью (§8.4.2) подписи mA .

Итак, давайте посмотрим на §8.4.2. Подпись метода :

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

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

m2 имеет такую ​​же подпись, что и m1, либо

подпись m1 совпадает с удаление (§4.6) подписи m2.

Две сигнатуры метода m1 и m2 эквивалентны переопределению, если либо m1 является подписью m2, либо m2 является подпись m1.

В нашем случае следует отметить пару вещей:

  1. Возвращаемое значение не является частью подписи, поэтому это в основном игнорируется при принятии решения о том, переопределяет ли один метод другой. Существуют дополнительные ограничения, основанные на типе возвращаемого значения, но эти ограничения не влияют, если два метода переопределяют друг друга, но если переопределение действительно компилируется. См. §8.4.8.3 Требования к переопределению и сокрытию .

  2. Эти две подписи не являются одинаковыми потому что они не имеют одинакового количества параметров типа.

  3. Они не являются подписями, потому что для этого требуется, чтобы одна была удалена другой, но обе подписи содержат общие c типы (List<String>). Обратите внимание, что это меняется, если в сигнатурах метода нет универсальных выражений, т. Е. Если вы используете List в качестве параметра или List<String> появляется только в возвращаемом значении.

=> метод в Derived не перекрывает метод в Base.

Но их удаление такое же. Стирание в основном удаляет все параметры типа. См. §4.6 Тип Erasure для очевидно более сложных деталей. Но здесь важно то, что удаление подписей: void f(List arg)

Это нарушает раздел в §8.4.8.3 Требования по переопределению и сокрытию :

Ошибка времени компиляции, если объявление типа T имеет метод-член m1 и существует метод m2, объявленный в T, или супертип T, такой, что все перечисленное ниже истинно:

  • m1 и m2 имеют одно и то же имя.
  • m2 доступно (§6.6) из T.
  • Подпись m1 не является подписью (§8.4.2 ) подписи m2.
  • Подпись m1 или некоторый метод m1 overrides (directly or indirectly)has the same erasure as the signature of m2 or some method m2` переопределяет (прямо или косвенно).

Конечно, это приводит нас к вопросу: почему используется странное определение subsignature?

Это на самом деле объясняется в §8.4.2. Подпись метода :

Понятие подписи предназначено для express взаимосвязи между двумя методами, подписи которых не идентичны, но в которых один может переопределить другой , В частности, он позволяет методу, сигнатура которого не использует обобщенные типы c, переопределять любую обобщенную версию этого метода. Это важно, чтобы разработчики библиотеки могли свободно генерировать методы независимо от клиентов, которые определяют подклассы или подынтерфейсы библиотеки.

0 голосов
/ 05 апреля 2020

Что ж, проблема в том, что ваше первое объявление является неполным:

<T> void f(List<String> arg) {}de here

Любой шаблон c, определенный до возвращаемого типа, обычно предполагается использовать в аргументе метода. Как это не используется; Из-за неполноты, компилятор причины видит метод в суперклассе по сравнению с дочерним классом как схожий сначала из-за тех же аргументов, но, как он видит, в суперклассе задействован обобщенный элемент c, который не участвует в дочернем классе, и он не может точно определить, что именно означает.

Типы возвращаемых данных не играют никакой роли для этого, например, попробуйте следующее все равно покажет ошибку:

class Base {
     <T> List<String> f(List<String> arg) { return null; }
}

class Derived extends Base {     
    List<String> f(List<String> arg) { return null; }
}

Я бы предложил полностью следовать синтаксису дженериков, чтобы не иметь такого странного поведения.

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