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

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

这段代码由github上的etorreborre提供。

4个回答

7
因为编译器无法确定一个 Long 是否是一个 Pongsuper,它是由一个 PingPing 继承自一个 PongPong,还是 PingPing 继承自一个扩展了 Pong 的某个东西的 Long ,但我可能是错误的。

2
你把我的舌头扭曲了... - Shawn Chin

6

显然,这是Java编译器中的一个错误。编译器不应该崩溃,尤其是在如此简短的程序上。

这甚至可能是Java语言规范中的漏洞;也就是说,对于JLS作者没有考虑到的泛型中的一个不起眼的边缘情况。

但是(在我看来),这只是一件好奇心,除非你能举出一个不那么明显的例子来故意破坏编译器。我的意思是,这个示例代码并不是特别有意义...


深入理解Java编译器实现的人可能可以找出为什么会导致堆栈溢出的原因。但这并不重要,除非该人也将修复该错误。除非有人能够提供一个触发相同问题的有意义的例子,否则我无法看到修复它的任何价值。


2
即使只是出于好奇,这确实是一个错误,应该提交报告:http://bugreport.sun.com/bugreport/ - Matteo
@Matteo - 如果你这样认为,就去做吧。但是,如果出现bug并且被给予极低的优先级而未被修复,我也不会感到惊讶。 - Stephen C
我会尝试,但需要在不同的操作系统上进行测试(Oracle不支持Mac OS X:我应该通过苹果提交申请,但修复的可能性非常低 :-))。 - Matteo
显然,Java的类型系统不是可决定的。请参见http://www.cs.cornell.edu/~ross/publications/tamewild/tamewild-tate-pldi11-tr.pdf,在此文中讨论了这个例子。 - michid

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编译器仍然能够编译该代码,因此看来它只是递归地做了某些事情,而最好做成迭代的方式。

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


0

我在JDK 8_u25中遇到了一些通用问题,升级到JDK 8u_65后问题得到解决。


在 jdk1.8.0_72 中似乎又出现了 bug? - MGorgon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接