如果一个人在不同目录中编写了两个具有相同的不区分大小写名称的公共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项目。

如果尝试,我在两个类上都调用a,如下所示:

System.out.println(A.myCase());
System.out.println(a.myCase());

类型检查器成功,但是当我运行直接由上面的代码生成的类文件时,我得到:

线程“主”中的异常java.lang.NoClassDefFoundError:testcase / A(错误的名称:testcase / a)

在Java中,名称通常区分大小写。 某些文件系统(例如Windows)不区分大小写,因此上述行为不会使我感到惊讶,但这似乎是错误的。 不幸的是,Java规范对于哪些类可见是不可置信的。 Java SE 7 Edition(Java语言规范,JLS)(第6.6.1节,第166页)说:

如果将类或接口类型声明为公共,则可以由以下对象访问 任何代码,条件是在其中声明了该代码的编译单元(第7.3节) 可观察的。

在7.3节中,JLS用非常模糊的术语定义了编译单元的可观察性:

预定义包java及其子包lang的所有编译单元 和io总是可以观察到的。 对于所有其他软件包,主机系统确定可以观察到哪些编译单元。

Java虚拟机规范同样含糊不清(第5.3.1节):

以下步骤用于加载并由此创建nonarray类或 使用引导程序类加载器的[二进制名称] N表示的接口C [...] 否则,Java虚拟机将参数N传递给对 引导类加载器上的方法来搜索C的自称表示 以平台相关的方式。

所有这些都导致四个问题的重要性降序:

是否可以保证每个JVM中的默认类加载器可以加载哪些类? 换句话说,我是否可以实现一个有效的但退化的JVM,它不会加载java.lang和java.io中的类之外的任何类?

如果有任何保证,那么上面示例中的行为是否违反了保证(即行为是否是错误)?

有什么方法可以同时使HotSpot加载a和A? 编写自定义类加载器会起作用吗?