如果在不同的目录中将两个具有相同的不区分大小写的公共Java类写入,那么这两个类在运行时都不可用.(我在Windows,Mac和Linux上使用多个版本的HotSpot JVM进行了测试.如果有其他JVM可以同时使用,我也不会感到惊讶.)例如,如果我创建一个名为class的类,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";
}
}
Run Code Online (Sandbox Code Playgroud)
如果尝试我这样调用myCase
这两个类:
System.out.println(A.myCase());
System.out.println(a.myCase());
Run Code Online (Sandbox Code Playgroud)
类型检查成功,但是当我运行由上面的代码生成的类文件时,我得到:
线程"main"中的异常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的声称表示.
所有这些都导致了四个问题,按重要性降序排列:
a
和A
同步?编写自定义类加载器会起作用吗?Don*_*ows 19
- 是否可以保证每个JVM中的引导类加载器可以加载哪些类?
语言的核心部分,以及支持实现类.不保证包括您编写的任何课程.(普通的JVM在一个单独的类加载器中将类加载到引导程序中,实际上正常的引导加载程序通常会从JAR中加载它的类,因为这样可以比一个充满类的大型旧目录结构更有效地进行部署.)
- 如果有任何保证,上述示例中的行为是否违反了保证(即行为是否为错误)?
- 有没有办法让"标准"JVM同时加载a和A?编写自定义类加载器会起作用吗?
Java通过将类的全名映射到文件名来加载类,然后在类路径中搜索该文件名.因此,testcase.a
前进testcase/a.class
和testcase.A
前进testcase/A.class
.有些文件系统将这些东西混合在一起,并且可以在需要时提供另一个文件系统.其他人做得对(特别是,JAR文件中使用的ZIP格式的变体完全区分大小写并且是可移植的).Java无法做到这一点(尽管IDE可以通过保持.class
文件远离本机FS 来为您处理它,我不知道是否有任何实际操作而且JDK javac
肯定不那么聪明).
然而,这不是唯一需要注意的地方:类文件在内部知道他们在谈论什么类.文件中缺少预期的类只意味着加载失败,导致NoClassDefFoundError
您收到.你得到的是一个问题(至少在某种意义上的错误部署),它被强有力地检测和处理.从理论上讲,你可以通过保持搜索来构建一个可以处理这类事情的类加载器,但为什么还要费心呢?将类文件放在JAR中会更加稳健地解决问题; 这些都是正确处理的.
更一般地说,如果你真的遇到了这个问题,那么在带有区分大小写的文件系统的Unix上进行生产构建(建议使用像Jenkins这样的CI系统)并找出哪些开发人员只是在区分大小写的情况下命名类并让它们停止,因为它非常混乱!