Наследование исходных типов с конфликтующими универсальными супер-интерфейсами
Я столкнулся с интересным фрагментом кода 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?
Ответы
Ответ 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 здесь не так.