如果一个人在不同的目录中使用相同的不区分大小写的名称编写两个公共 Java 类,那么这两个类在运行时都不可用。(我在 Windows、Mac 和 Linux 上使用多个版本的 HotSpot JVM 进行了测试。如果有其他 JVM 可以同时使用它们,我不会感到惊讶。)例如,如果我创建一个名为的类a
,并且一个命名A
如下:
// lowercase/src/testcase/a.java
package testcase;
public class a {
public static String myCase() {
return "lower";
}
}
// uppercase/src/testcase/A.java
package testcase;
public class A {
public static String myCase() {
return "upper";
}
}
我的网站上提供了三个包含上述代码的 Eclipse 项目。
如果尝试我像这样调用myCase
两个类:
System.out.println(A.myCase());
System.out.println(a.myCase());
类型检查器成功了,但是当我运行由上面的代码直接生成的类文件时,我得到:
线程“主”java.lang.NoClassDefFoundError 中的异常:testcase/A(错误名称:testcase/a)
在 Java 中,名称通常区分大小写。一些文件系统(例如 Windows)不区分大小写,所以我对上述行为的发生并不感到惊讶,但它似乎是错误的。不幸的是,Java 规范对于哪些类是可见的很奇怪。Java 语言规范 (JLS),Java SE 7 版(第6.6.1 节,第 166 页)说:
如果一个类或接口类型被声明为 public,那么它可以被任何代码访问,只要声明它的编译单元(第 7.3 节)是可观察的。
在第 7.3 节中,JLS 以极其模糊的术语定义了编译单元的可观察性:
预定义包 java 及其子包 lang 和 io 的所有编译单元始终是可观察的。对于所有其他包,主机系统确定哪些编译单元是可观察的。
Java 虚拟机规范同样含糊不清(第 5.3.1 节):
以下步骤用于使用引导类加载器加载并创建由 [二进制名称] N 表示的非数组类或接口 C [...] 否则,Java 虚拟机将参数 N 传递给引导类加载器以依赖于平台的方式搜索 C 的声称表示。
所有这些都导致了四个问题,按重要性降序排列:
- 是否可以保证每个 JVM 中的默认类加载器可以加载哪些类?换句话说,我能否实现一个有效但退化的 JVM,它不会加载除 java.lang 和 java.io 中的类之外的任何类?
- 如果有任何保证,上面示例中的行为是否违反了保证(即该行为是否是错误)?
- 有什么方法可以同时加载 HotSpot
a
吗A
?编写自定义类加载器会起作用吗?