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

Наследование исходных типов с конфликтующими универсальными супер-интерфейсами

Я столкнулся с интересным фрагментом кода Java, который указывает IntelliJ как ошибку, но который javac принимает как законный. Либо IntelliJ ошибается, и код является законным, либо компилятор является "неправильным", будь то из-за ошибки или преднамеренного ослабления правил.

Мне нравится думать, что я хорошо понимаю систему типов Java, и мои собственные рассуждения заставляют меня подозревать, что IntelliJ ошибается и javac прав. Тем не менее, у меня есть чертовски время, грохочущее JLS, и я хотел бы знать наверняка.

Прежде чем попасть в проблемный код, давайте посмотрим на какой-то похожий код определенно незаконный:

interface A<T> {}
interface X extends A<String> {}
interface Y extends A<Object> {}

interface Z extends X, Y {} // COMPILE ERROR

Как и следовало ожидать, как IntelliJ, так и javac правильно отмечают это как ошибку: "A" не может быть унаследован разными аргументами типа: "java.lang.String" и "java". lang.Object".

Нет проблем. Но что делать, если мы делаем X и Y generic, причем Z расширяем свою необработанную форму?

interface X<T> extends A<String> {}
interface Y<T> extends A<Object> {}

interface Z extends X, Y {}  // OK according to javac, ERROR according to IntelliJ

Здесь IntelliJ с готовностью сообщает ту же ошибку, что и для первого фрагмента, но javac с радостью принимает код как написано.

Я понимаю, что исходные типы рекурсивно стираются, что означает, что все суперклассы и суперинтерфейсы исходного типа заменяются на их рекурсивно стираемые формы и т.д. Таким образом, в проблемном коде Z заканчивается расширением (raw) A через X и Y, в отличие от первого примера, в котором Z продолжается A<String> через X и A<Object> через Y.

Если это действительно так, я бы сделал вывод, что IntelliJ ошибочен, а javac верен: второй фрагмент кода является законным.

Что скажешь, эксперты Stack Overflow?

4b9b3361

Ответ 1

Спектр говорит в JLS-8.1.5:

В то же время класс не может быть подтипом двух типов интерфейса, которые являются разными параметризациями одного и того же общего интерфейса (§9.1.2) или подтипом параметризации общего интерфейса и именного типа, который такой же общий интерфейс или ошибка времени компиляции.

Обратите внимание, что в нем особо отмечается случай "подтипа параметризации общего интерфейса и необработанного типа, называющего тот же общий интерфейс", который переводится как-то вроде interface Z extends A<String>, A {}. Случай с 2 ​​необработанными суперинтерфейсами не упоминается.

Кроме того, спецификация дает следующий пример:

interface I<T> {}
class B implements I<Integer> {}
class C extends B implements I<String> {}

Класс C вызывает ошибку времени компиляции, поскольку он пытается быть подтипом как I<Integer>, так и I<String>.

Проблема заключается в том, что A расширяется с помощью различных аргументов типа, если X и Y оба должны были расширять A<Object> в вашем первом фрагменте, который также компилируется.

JLS-4.8 говорит (как вы уже упоминали):

Суперклассы (соответственно суперинтерфейсы) необработанного типа - это стирания суперклассов (суперинтерфейсов) любой из параметризации родового типа.

Это означает, что Z удваивает необработанный тип A, а не A с разными параметризациями. Более того, класс для класса имеет тот же (косвенный) суперинтерфейс дважды (см. Второй пример в JLS-8.1.5-2).

Итак, я пришел к выводу, что Intellij здесь не так.