为什么以下代码会使 javac 崩溃?我们能做些什么呢?

我正在阅读这篇关于“Java中的奇怪事物”的文章,我遇到了一个有趣的概念:不可决定的类型。

请考虑以下三个类/接口:

public interface Type<T> { }
public class D<P> implements Type<Type<? super D<D<P>>>> { }
public class WildcardTest {
  Type<? super D<Byte>> d = new D<Byte>();
}

显然,问题是,是否是 ;任何人都可以进一步解释这一点吗?DType<? super D<Byte>>

javac 1.8.0_60在尝试编译时抛出很长的时间:StackOverflowErrorWildcardTest

The system is out of resources.
Consult the following stack trace for details.
java.lang.StackOverflowError
        at com.sun.tools.javac.code.Types$UnaryVisitor.visit(Types.java:4640)
        at com.sun.tools.javac.code.Types$26.visitClassType(Types.java:3834)
        at com.sun.tools.javac.code.Types$26.visitClassType(Types.java:3826)
        at com.sun.tools.javac.code.Type$ClassType.accept(Type.java:778)
        at com.sun.tools.javac.code.Types$UnaryVisitor.visit(Types.java:4640)
        at com.sun.tools.javac.code.Types$26.visitClassType(Types.java:3839)
        at com.sun.tools.javac.code.Types$26.visitClassType(Types.java:3826)
        at com.sun.tools.javac.code.Type$ClassType.accept(Type.java:778)
        at com.sun.tools.javac.code.Types$UnaryVisitor.visit(Types.java:4640)
        at com.sun.tools.javac.code.Types$26.visitClassType(Types.java:3839)
        at com.sun.tools.javac.code.Types$26.visitClassType(Types.java:3826)
        at com.sun.tools.javac.code.Type$ClassType.accept(Type.java:778)
        at com.sun.tools.javac.code.Types$UnaryVisitor.visit(Types.java:4640)
        at com.sun.tools.javac.code.Types$26.visitClassType(Types.java:3839)
        at com.sun.tools.javac.code.Types$26.visitClassType(Types.java:3826)
        at com.sun.tools.javac.code.Type$ClassType.accept(Type.java:778)

此代码还会使整个 Eclipse IDE 崩溃。

作者已经向Eclipse团队提交了一个错误,但它没有得到任何投票(除了我的)。什么都能做到吗?这仅仅是编译器形式的停止问题吗?

文章中还有一个关于它的论文链接,但我希望有一个更直接的解释。


答案 1

正如Tunaki在评论中指出的那样,这可以追溯到Pierce(TAPL作者)共同撰写的微软研究论文。事实上,Tate等人给出的问题是附录A中的示例2(带有,和)。Byte = TType = ND = C

堆栈溢出

首先,让我们弄清楚它为什么会吹堆栈。要做到这一点,最好提醒自己,编译器检查类型的方式与我们几乎相同。我们遇到的问题它会遇到。

// To be determined:
D<Byte> <: Type<? super D<Byte>>

// using D<P> implements Type<Type<? super D<D<P>>>>

Type<Type<? super D<D<Byte>>>> <: Type<? super D<Byte>>

// The outermost type constructor (Type) matches. For the
// suptyping relationship to hold, we have to test the type
// arguments. (Sides are flipped due to contravariance.)

D<Byte> <: Type<? super D<D<Byte>>>

// Mhh… That looks an awful lot like above.
// Feel free to rinse and repeat until your "stack" blows too… ;)

Pierce等人描述了一个类似的回归,如他们论文第3节中的示例2。它略有不同,因为Java不允许对类型变量使用下限,只允许对通配符使用下限。

我们能做些什么?

与Pierce等人给出的示例1类似,这种回归遵循一种模式。编译器可以检测到这种模式,并且可以假定子类型关系在协纳解释下成立。(这与 F 有界多态性具有相同的推理,即 .你进入一个类似的无限回归,没有矛盾。Enum<E extends Enum<E>>

无法决定?

给定的问题可以通过更智能的算法来解决。Java的类型系统是否可判定仍然是一个悬而未决的问题。

如果Java允许类型参数的下限,它将是不可决定的(半可判定的)。然而,在他们的论文Pierce等人中,定义了限制,通过这些限制,这种类型系统可以再次决定。顺便说一句,Scala采用了这些限制,具有讽刺意味的是,Scala具有图灵完备类型系统。


答案 2