为什么编译此代码会导致编译器堆栈溢出?

时间:2011-11-23 09:46:20

标签: java compilation stack-overflow

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)
    ...

Code courtesy of etorreborre on github.

4 个答案:

答案 0 :(得分:6)

显然,这是Java编译器中的一个错误。编译器不应该崩溃,特别是在程序这么小的情况下。

甚至可能是Java语言规范中的一个漏洞;即JLS作者未考虑的泛型中的一个模糊边缘案例。

但是(IMO)这只不过是一种好奇心,除非你能想出一个不那么明显设计来打破编译器的例子。我的意思是,这个示例代码并不完全有意义......


对Java编译器实现有深刻理解的人可能会弄清楚为什么会导致堆栈溢出。但除非那个人也要解决这个问题,否则它几乎不相关。除非有人能提出一个触发同样问题的有意义的例子,否则我看不出修复它的任何价值。

答案 1 :(得分:6)

因为编译器无法确定LongPongsuper Ping PingLong或者它是Ping Ping的某个Pong延伸Pong {{1}} ...但我可能错了。

答案 2 :(得分:2)

我有一个同事在实际代码中有类似的问题。在那里,他有一个带有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> {}

此代码实际编译。实际上,不仅有2个子类,而是更深层次的类型,例如VImpl和EImpl的三个变体,每个变体都有任意多个子类。嗯,实际上,有3种类型的参数,如上所示,限制有点复杂。

当VImpl和EImpl只有两个变种时,它仍然编译得很好。一旦添加了第三个变体,他就会在编译器中获得堆栈溢出。也就是说,Eclipse编译器仍然能够编译代码,因此它似乎只是 javac 以递归方式执行某些操作,最好是迭代执行。

不幸的是,我们无法将完整的代码库缩减为适合错误报告的最小示例......

答案 3 :(得分:0)

我在JDK 8_u25中遇到了一些通用的东西,更新到JDK 8u_65 解决了这个问题。