【问题标题】:javac classpath order contradicts Oracle documentation?javac 类路径顺序与Oracle 文档相矛盾?
【发布时间】:2016-11-24 18:22:36
【问题描述】:

Sierra/Bates SCJP书页797:

"java 和 javac [...] 首先查看包含 Java SE 标准类的目录。 然后他们在类路径定义的目录中查找"

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:错误:找不到符号 a.add("大声笑"); ^ 符号:方法添加(字符串) 位置:HashSet类型的变量a

...表示首先使用默认的类路径(当前目录)。

那么,Oracle 文档有错吗?您将如何测试类路径顺序?

【问题讨论】:

标签: java jvm


【解决方案1】:

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

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

(来自How Classes are Found : How Javac and JavaDoc Find Classes:)

如果在类文件和源文件中都定义了引用的类,[...] javac 会使用类文件,但会自动重新编译它确定为过期的任何类文件。自动重新编译的规则记录在 WindowsSolaris 的 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 类。

【讨论】:

  • 好的,谢谢。源文件是第一位的。然后“编译器首先在引导类和扩展类中搜索类文件,然后在用户类路径中搜索”(如 Sierra/Bates 和 Oracle 文档中的正确所述)
【解决方案2】:

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

唯一可以理解的行是:

默认情况下,javac 和 javadoc 在用户类路径中搜索类文件和源代码文件。

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

他们更明确here

JDK、JVM 和其他 JDK 工具通过按顺序搜索 Java 平台(引导)类、任何扩展类和类路径来查找类。 (有关搜索策略的详细信息,请参阅如何找到类。)

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

附带说明,您将无法调用在 java.util 包中创建的用户定义类,因为该包和其他标准包(即 java.*)是受限包,您将获得一个尝试调用这些包中定义的用户类时出现异常(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 保留了两个不同的路径列表,一个用于源文件,一个用于类文件。虽然 类文件 的行为在文档中有所描述,但 javac 首先检查特定对象的 .java 源文件是否存在,以及是否有任何 javac 编译/加载该用户定义的类。因此,关于类加载,该行实际上是正确的,它们只是没有指定编译期间的全局顺序(来自类路径的源+类)。来源永远是第一位的。

【讨论】:

  • 嗨。根据有关 javac 的 Oracle 文档,它的行为方式与 java 相同。 “编译器首先在引导和扩展类中搜索类文件,然后在用户类路径中搜索”。在此声明:docs.oracle.com/javase/7/docs/technotes/tools/windows/…
  • 等等,我正在用一些发现更新我的答案:)
  • 阅读@Marco13 的回答,他不只是像我一样浏览文档:)
猜你喜欢
  • 2018-07-22
  • 1970-01-01
  • 2017-07-04
  • 2020-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-14
相关资源
最近更新 更多