参考 Oracle 文档,SCJP 书中的陈述可能过于简单。 Oracle 文档明确区分了“Java 启动器”(java) 和 Java 编译器 javac。事实上,这些过程有些不同。
我将尝试提取解释您观察到的行为的相关部分:
(来自How Classes are Found : How Javac and JavaDoc Find Classes:)
如果在类文件和源文件中都定义了引用的类,[...] javac 会使用类文件,但会自动重新编译它确定为过期的任何类文件。自动重新编译的规则记录在 Windows 或 Solaris 的 javac 文档中。
这些链接的文档包含相应的小节(在两种情况下都是相同的),我将在这里再次引用:
(来自javac - Java programming language compiler : SEARCHING FOR TYPES:)
编译源文件时,编译器通常需要有关其定义未出现在命令行给出的源文件中的类型的信息。 [...]
当编译器需要类型信息时,它会寻找定义类型的源文件或类文件。 [...]
成功的类型搜索可能会产生一个类文件、一个源文件或两者。如果两者都找到,您可以使用-Xprefer 选项来指示编译器使用哪个。如果给出 newer,编译器将使用两个文件中较新的一个。如果给出 source,它将使用源文件。默认为较新。
如果类型搜索找到所需类型的源文件,无论是单独找到源文件,还是作为-Xprefer 设置的结果,编译器将读取源文件以获取所需的信息。此外,默认情况下它也会编译源文件。您可以使用-implicit 选项来指定行为。如果给出 none,则不会为源文件生成类文件。如果给出class,将为源文件生成类文件。
总结一下:javac 编译器会找到您的java.util.HashSet 的源 文件,以及引导类中的class 文件。但默认情况下,它会编译 source 文件。
(有趣的是,似乎没有简单的方法可以说服他不要将源用作输入:-implicit 选项仅确定 .class 文件是否生成,但即使如果设置了-implicit:none,它仍将使用从源创建的类...)
您还可以使用-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
它甚至没有尝试从引导 JAR 加载 HashSet 类,而是直接引用您的源文件:
[loading RegularFileObject[.\java\util\HashSet.java]]
相比之下,当您省略自己的 HashSet 类时,您将看到预期的输出:
[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 类。