JEP 216:正确处理进口声明
概括
修复以正确接受和拒绝程序,无论语句和子句javac
的顺序如何。import``extends``implements
动机
在某些情况下,javac
将接受具有特定导入顺序的源代码,并拒绝仅重新排序导入的相同源代码(例如,JDK-7177813)。这是错误且令人困惑的。
描述
javac
编译类时使用多个阶段。考虑到import
处理,两个重要的阶段是:
-
类型解析,通过提供的 AST 查找类和接口声明,以及
-
会员决议,包括:
- ( 1a ) 如果
T
是 toplevel,import
则源文件定义中的 sT
将被处理,并将导入的成员添加到T
的范围内 - ( 1b ) 如果
T
是嵌套的,则解析直接包含的类T
(如果有) - ( 2 )
extends
/implements
的子句T
经过类型检查 - ( 3 ) 的类型变量
T
进行类型检查
- ( 1a ) 如果
上述阶段是类_解析_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.Outer
、P.Outer.Nested
和P.Q.I
。然后,如果P.Outer
要分析类,成员解析阶段的工作方式如下:
P.Outer
启动分辨率
按照 1a处理开头,这意味着查找import static P.Outer.Nested.*;
其成员及其传递超类型。P.Outer.Nested
类的解析P.Outer.Nested
开始(静态导入也可以导入继承的类型)
触发 的解析P.Outer
,由于该解析已经在进行中,因此被跳过
I
(子句)的类型检查implements
运行,但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
成员解析分为三个:第一个阶段将分析封闭文件的导入,第二个阶段将仅构建类/接口层次结构,没有 任何类型参数、注释_等。_,第三个将正确分析类头,包括类型参数。
预计此更改将允许javac
接受当前被拒绝的计划,但不会拒绝当前已接受的计划。