javac 类路径顺序与 Oracle 文档相矛盾?

javac classpath order contradicts Oracle documentation?

Sierra/Bates SCJP 书第 797 页中:

"java and javac [...] first look in the directories that contain the classes that come standard with Java SE. Then they look in the directories defined by classpaths"

Oracle documentation 表示相同的命令。

(我知道我不应该那样做,但是...)为了测试这种行为,我实施了 HashSet.javaLol.java 在目录中: C:\dev\cmdline\TestProject\sources\java\util

package java.util;
public class HashSet {}

package java.util;
import java.util.HashSet;
public class Lol {
    public static void main(String... x) {
        HashSet a = new HashSet();
        a.add("lol");
    }
}

执行时出现编译错误: C:\dev\cmdline\TestProject\sources>javac java/util/Lol.java

java\util\Lol.java:6: error: cannot find symbol a.add("lol"); ^ symbol: method add(String) location: variable a of type HashSet

...这意味着首先使用默认类路径(当前目录)。

所以,是不是Oracle文档有误?您将如何测试类路径顺序?

JVM 和 Java 编译器是两个截然不同的东西,而 SCJP 描述的行为是 JVM 所期望的行为,我不太确定 javac 遵循相同的规则,在您链接的 Sun 技术说明中没有明确说明两者遵循相同的方法。

唯一可以理解的行是:

By default, javac and javadoc search the user class path for both class files and source code files.

但它没有指定关于 class 在其他路径中搜索的任何顺序。

它们更明确 here:

The JDK, the JVM and other JDK tools find classes by searching the Java platform (bootstrap) classes, any extension classes, and the class path, in that order. (For details on the search strategy, see How Classes Are Found.)

唯一重要的文件是 JVM Specification,但不包含有关 java 工具的详细信息。

作为旁注,请注意您将无法调用在 java.util 包中创建的用户定义的 classes,因为该包和其他标准包(即 java.*) 是受限制的包,当你试图调用在这些包中定义的用户 class 时你会得到一个异常(javac 无论如何都会编译你的代码,不执行检查,但是 jvm会抱怨)。

编辑:

编译您的示例并添加 -verbose 选项:

javac -verbose java/util/Lol.java

[parsing started RegularFileObject[java/util/Lol.java]]
[parsing completed 37ms]
***[search path for source files: .]
[search path for class files: /Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/jre/lib/resources.jar,/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/jre/lib/rt.jar,[...],.]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Object.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/String.class)]]
***[checking java.util.Lol]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/io/Serializable.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/AutoCloseable.class)]]
***[loading RegularFileObject[./java/util/HashSet.java]]
***[parsing started RegularFileObject[./java/util/HashSet.java]]
[parsing completed 0ms]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Byte.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Character.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Short.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Long.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Float.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Integer.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Double.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Boolean.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Void.class)]]
java/util/Lol.java:8: error: cannot find symbol
        a.add("lol");
         ^
symbol:   method add(String)
location: variable a of type HashSet
[checking java.util.HashSet]
[total 449ms]
1 error

查看带有 *** 的行,如您所见,javac 保留两个不同的路径列表,一个用于源,一个用于 class 文件。虽然 class files 的行为是文档中描述的行为,但 javac 首先检查 .java 源文件是否为特定对象存在并且如果有 javac compile/loads 用户定义 class。因此,关于 class 加载,该行实际上是正确的,他们只是没有指定编译期间的全局顺序(sources+classes 来自 classpath)。资源总是第一位的。

参考 Oracle 文档,SCJP 书中的陈述可能过于简单化。 Oracle 文档明确区分了“Java 启动器”(java) 和 Java 编译器 javac。事实上,过程有些不同。

我将尝试提取解释您所观察到的行为的相关部分:

(From How Classes are Found : How Javac and JavaDoc Find Classes:)

If a referenced class is defined in both a class file and source file, [...] javac uses class files, but automatically recompiles any class files it determines to be out of date. The rules for automatic recompilation are documented in the javac document for Windows or Solaris.

这些链接文档包含相应的小节(在两种情况下都是相同的),我将在此处再次引用:

(From javac - Java programming language compiler : SEARCHING FOR TYPES:)

When compiling a source file, the compiler often needs information about a type whose definition did not appear in the source files given on the command line. [...]

When the compiler needs type information, it looks for a source file or class file which defines the type. [...]

A successful type search may produce a class file, a source file, or both. If both are found, you can use the -Xprefer option to instruct the compiler which to use. If newer is given, the compiler will use the newer of the two files. If source is given, it will use the source file. The default is newer.

If a type search finds a source file for a required type, either by itself, or as a result of the setting for -Xprefer, the compiler will read the source file to get the information it needs. In addition, it will by default compile the source file as well. You can use the -implicit option to specify the behavior. If none is given, no class files will be generated for the source file. If class is given, class files will be generated for the source file.

总结一下:javac 编译器将为 java.util.HashSet 以及 [=66] 找到您的 source 文件=] 文件来自 bootstrap classes。但默认情况下,它将编译 source 文件。

(有趣的是,似乎没有简单的方法可以说服他不使用源作为输入:-implicit 选项仅确定 .class 文件是否为 生成,但即使设置了-implicit:none,它仍然会使用从源创建的class...)

您还可以使用 -verbose 选项来更详细地观看此过程:

javac -verbose java/util/Lol.java

产生以下输出:

[parsing started RegularFileObject[java\util\Lol.java]]
[parsing completed 100ms]
[search path for source files: .]
[search path for class files: (A long list with rt.jar and related JARs)]
[loading RegularFileObject[.\java\util\HashSet.java]]
[parsing started RegularFileObject[.\java\util\HashSet.java]]
[parsing completed 0ms]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Object.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/String.class)]]
[checking java.util.Lol]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/AutoCloseable.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Byte.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Character.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Short.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Long.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Float.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Integer.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Double.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Boolean.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Void.class)]]
java\util\Lol.java:6: error: cannot find symbol
        a.add("lol");
         ^
  symbol:   method add(String)
  location: variable a of type HashSet
[checking java.util.HashSet]
[total 1072ms]
1 error

它甚至不尝试从bootstrap JAR 加载 HashSet`class,而是直接引用您的源文件:

[loading RegularFileObject[.\java\util\HashSet.java]]

相比之下,当您省略自己的 HashSet class 时,您将看到预期的输出:

[parsing started RegularFileObject[java\util\Lol.java]]
[parsing completed 100ms]
[search path for source files: .]
[search path for class files: (A long list with rt.jar and related JARs) ]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/util/HashSet.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Object.class)]]
...

它从 rt.jar.

获得 HashSet class