Почему компиляция этого кода вызывает переполнение стека компилятора?
interface Pong<T> {}
class Ping<T> implements Pong<Pong<? super Ping<Ping<T>>>> {
static void Ping() {
Pong<? super Ping<Long>> Ping = new Ping<Long>();
}
}
Попытка скомпилировать это дает ошибку:
The system is out of resources.
Consult the following stack trace for details.
java.lang.StackOverflowError
at com.sun.tools.javac.code.Types$23.visitClassType(Types.java:2579)
at com.sun.tools.javac.code.Type$ClassType.accept(Type.java:554)
at com.sun.tools.javac.code.Types$UnaryVisitor.visit(Types.java:3260)
at com.sun.tools.javac.code.Types$23.visitClassType(Types.java:2592)
at com.sun.tools.javac.code.Types$23.visitClassType(Types.java:2579)
at com.sun.tools.javac.code.Type$ClassType.accept(Type.java:554)
...
Код предоставлен etorreborre на github.
Ответы
Ответ 1
Очевидно, что это ошибка в компиляторе Java. Компилятор не должен терпеть крах, особенно в такой маленькой программе.
Это может быть даже ямкой в Спецификации языка Java; то есть неясным краевым случаем в дженериках, которые авторы JLS не рассматривали.
Но (IMO) это не что иное, как любопытство, если вы не придумаете пример, который не так явно умудрился разбить компилятор. Я имею в виду, что этот примерный код не имеет особого значения...
Кто-то, кто глубоко понимает реализацию компилятора Java, может, вероятно, выяснить, почему это вызывает переполнение стека. Но это вряд ли актуально, если этот человек также не исправит ошибку. И если кто-то не придумает осмысленный пример, который вызывает ту же проблему, я не вижу никакого смысла в его исправлении.
Ответ 2
Поскольку компилятор не может решить, является ли Long
Pong
, который является super
для Ping
для Ping
для Long
, или является Ping
для Ping
того, что расширяет a Pong
a Pong
... но я могу ошибаться.
Ответ 3
У меня есть коллега, которая имеет аналогичную проблему в реальном коде. Там он имеет абстрактный базовый класс с 2 параметрами типа, который имеет два подкласса, фиксирующих их для конкретных типов. В основном это позволяет определить полную логику в абстрактном классе вместо того, чтобы дублировать код в обоих подклассах с замененными конкретными типами.
В принципе, код есть что-то в этом роде:
public abstract class AImpl<X extends A<Y>, Y extends A<X>> {
public X foo(Y o) {
return o.doStuff();
}
public Y bar(X o) {
return o.doStuff();
}
}
class VImpl extends AImpl<V, E> {}
class EImpl extends AImpl<E, V> {}
interface A<T> {
T doStuff();
}
interface V extends A<E> {}
interface E extends A<V> {}
Этот код действительно компилируется. На самом деле существует не только два подкласса, но и более глубокая иерархия типов, например три варианта VImpl и EImpl, каждая из которых имеет произвольное множество подклассов. Ну и на самом деле есть 3 типа параметров, и ограничения немного сложнее, как показано выше.
Когда было только два варианта VImpl и EImpl, он все еще был скомпонован. Как только были добавлены третьи варианты, он получил переполнение стека в компиляторе. Тем не менее, компилятор Eclipse все еще способен скомпилировать код, поэтому кажется, что он просто javac делает что-то рекурсивно, что лучше делать итеративно.
К сожалению, мы не можем уменьшить полную базу кода до некоторого минимального примера, подходящего для отчета об ошибке...
Ответ 4
Я испытал то же самое с некоторыми родовыми вещами в JDK 8_u25, обновление до JDK 8u_65 разрешило проблему.