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

interface Pong {} class Ping implements Pong<Pong<? super Ping<Ping>>> { static void Ping() { Pong<? super Ping> Ping = new Ping(); } } 

尝试编译这会给出错误:

 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提供。

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

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

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


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

因为编译器无法判断Long是否是一个Pongsuper Pongsuper Pong ,或者它是否是PingPong之类的Pong …但我可能是错的。

我有一个同事在实际代码中有类似的问题。 在那里,他有一个带有2个类型参数的抽象基类,它有两个子类,将它们固定到具体类型。 基本上,这允许在抽象类中定义完整的逻辑,而不必在具有交换的具体类型的两个子类中复制代码。

基本上,代码是这样的:

 public abstract class AImpl, Y extends A> { public X foo(Y o) { return o.doStuff(); } public Y bar(X o) { return o.doStuff(); } } class VImpl extends AImpl {} class EImpl extends AImpl {} interface A { T doStuff(); } interface V extends A {} interface E extends A {} 

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

当VImpl和EImpl只有两个变种时,它仍然编译得很好。 一旦添加了第三个变体,他就会在编译器中获得堆栈溢出。 也就是说,Eclipse编译器仍然能够编译代码,所以看起来它只是javac递归地执行某些操作,它应该更好地迭代执行。

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

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