跳到主要内容

JEP 216:正确处理导入语句

QWen Max 中英对照

概述

修复 javac,使其能够正确接受和拒绝程序,而不受 import 语句以及 extendsimplements 子句顺序的影响。

动机

在某些情况下,javac 会接受具有特定导入顺序的源代码,但如果仅仅重新排列了导入顺序,它却会拒绝相同的源代码(例如,JDK-7177813)。这是错误且令人困惑的行为。

描述

javac 在编译类时使用了多个阶段。考虑到 import 的处理,其中两个重要的阶段是:

  • 类型解析,它会查看提供的 AST(抽象语法树),寻找类和接口声明,并且

  • 成员解析,其中包括:

    • (1a) 如果 T 是顶级类,处理定义 T 的源文件中的 import 语句,并将导入的成员添加到 T 的作用域中
    • (1b) 如果 T 是嵌套类,则解析直接包含 T 的类(如果有的话)
    • (2) 对 Textends/implements 子句进行类型检查
    • (3) 对 T 的类型变量进行类型检查

上述阶段是 javac 的类解析过程的一部分,其中包含确定类的超类型、类型变量和成员。

要查看此过程的实际运作情况,请考虑以下代码:

package P;

import static P.Outer.Nested.*;
import P.Q.*;

public class Outer {
public static class Nested implements I {
}
}

package P.Q;
public interface I {
}

在类型解析阶段,会识别出存在类型 P.OuterP.Outer.NestedP.Q.I。然后,如果要分析 P.Outer 类,则成员解析阶段的工作方式如下:

P.Outer 的解析开始。

根据 1a,开始处理 import static P.Outer.Nested.*;,这意味着将查找 P.Outer.Nested 及其传递超类型的成员。

P.Outer.Nested 类的解析开始(静态导入也可以导入继承的类型)

触发解析 P.Outer,由于它已经在进行中,因此被跳过。

类型检查 Iimplements 子句)运行时,由于 I 尚未在作用域中,因此无法解析。

import P.Q.* 的解析开始,它将 P.Q 的所有成员类型(包括接口 I)导入到当前文件的作用域中。

P.Outer 和其他类的解析继续进行。

如果导入顺序被交换,那么第 6 步会在第 5 步之前发生,因此 I 会在第 5 步被找到。

上述问题并不是与 import 处理相关的唯一问题。另一个已知的问题是,类的类型参数的边界可能合法地引用其声明类中可能存在的内部类。在某些情况下,这目前会导致无法解析的循环,例如:

package P;

import static P.Outer.Nested.*;

public class Outer {
public static class Nested<T extends I> {
static class I { }
}
}

针对这个问题,设想的解决方案是将现有的 javac 成员解析的第一阶段拆分为三个部分:第一部分将分析包含文件的导入(imports),第二部分将仅构建类/接口层次结构,不涉及任何类型参数、注解等,第三部分则会完整地分析类头(class headers),包括类型参数。

预计这一变化将使 javac 能够接受目前被拒绝的程序,但不会拒绝目前被接受的程序。