如果在不同的目录中编写两个名称相同且不区分大小写的公共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项目是available from my website。
如果尝试在两个类上调用myCase
,如下所示:
System.out.println(A.myCase());
System.out.println(a.myCase());
类型检查器成功了,但是当我运行上面代码生成的类文件时,我得到:
执行绪“main”java. lang中发生例外状况。NoClassDefFoundError:测试用例/A(名称错误:测试用例/a)
在Java中,名称通常是区分大小写的。一些文件系统(例如Windows)是区分大小写的,所以发生上述行为我并不感到惊讶,但它似乎是"错误的"。不幸的是,Java规范奇怪地没有承诺哪些类是可见的。Java Language Specification (JLS), Java SE 7 Edition(第6.6.1节,第166页)说:
如果一个类或接口类型被声明为公共的,那么它可以被任何代码访问,只要声明它的编译单元(参见7.3节)是可观察的。
在7.3节中,JLS用非常模糊的术语定义了编译单元的可观察性:
所有预定义的java包及其子包lang和io的编译单元都是可观察的。对于所有其他包,主机系统决定哪些编译单元是可观察的。
Java Virtual Machine Specification同样模糊(第5.3.1节):
以下步骤用于使用引导类加载器[...]加载并由此创建由[二进制名] N表示的非数组类或接口C。否则,Java虚拟机将参数N传递给引导类加载器上的方法调用,以平台相关的方式搜索C的声称表示。
所有这一切引出了按重要性递减顺序排列的四个问题:
- Are there any guarantees about which classes are loadable by the default class loader(s) in every JVM? In other words, can I implement a valid, but degenerate JVM, that won't load any classes except those in java.lang and java.io?
1.如果有任何保证,上面例子中的行为是否违反了保证(即行为是否是bug)?
1.有没有办法让HotSpot同时加载a
和A
?编写一个自定义类加载器可以吗?
4条答案
按热度按时间t9aqgxwy1#
语言的核心部分,加上支持的实现类。不保证包括你编写的任何类。(普通JVM将你的类加载到一个独立于 Bootstrap 的类加载器中,实际上,普通 Bootstrap 加载器通常从JAR中加载类,因为这比一个充满类的大的旧目录结构更有效地进行部署。)
Java通过将类的全名Map到一个文件名来加载类,然后在类路径中搜索该文件名。因此
testcase.a
到testcase/a.class
,testcase.A
到testcase/A.class
。一些文件系统将这两个东西混在一起,当需要一个时可能会提供另一个。其他文件系统则做得很好(特别是,JAR文件中使用的ZIP格式的变体是完全区分大小写和可移植的)。(尽管IDE可以通过将.class
文件与原生FS分开来为您处理这个问题,但我不知道是否真的这样做了,而且JDK的javac
肯定没有那么聪明)。然而,这不是这里要注意的唯一一点:class文件内部知道它们在谈论什么类,文件中缺少 expected 类意味着加载失败,导致您收到
NoClassDefFoundError
。(至少在某种意义上是错误的部署)。理论上,您可以构建一个类加载器,通过不断搜索来处理此类事情,但是 * 为什么要这么麻烦呢?* 将类文件放在JAR中会更健壮地修复问题;这些都得到正确处理。更一般地说,如果您真实的遇到这个问题,那么就在Unix上使用区分大小写的文件系统(建议使用Jenkins这样的CI系统)进行生产构建,* 找出哪些开发人员在命名类时只区分大小写,让他们停止,因为这非常混乱!*
eyh26e7m2#
多纳尔的精彩解释没有留下什么可补充的,但让我简要地思考一下这句话:
...名称不区分大小写的Java类相同...
名称和字符串通常都是不区分大小写的,只有解释才可以。其次,Java不做这样的解释。
所以,你脑海中的正确措辞应该是:
...在不区分大小写的文件系统中其文件表示具有相同名称的Java类...
5jvtdoz23#
我试着从一个类名中添加或删除一个字符,它起作用了。我觉得使用不同的类名总是更好的。
dzhpxtsq4#
不要只考虑文件夹。
为类使用显式的不同名称空间(“包”),也可以使用文件夹来匹配类。
当我提到“软件包”时,我并不是指“*.JAR”文件,而是,仅仅是概念:
当你没有为你的代码指定一个包的时候,java工具(编译器,I.D.E.,无论什么),假设所有的都使用相同的全局包。而且,如果有几个相似的类,它们有一个文件夹列表,可以在那里查找。
包就像代码中的“虚拟”文件夹,适用于类路径或Java安装中的所有包。您可以有多个类,具有相同的ID,但是,如果它们位于不同的包中,并且您指定了要查找的包,则不会有任何问题。
就我的两分钱,买你的咖啡