Java 类名区分大小写

2022-01-16 00:00:00 jvm java jls

如果一个人在不同的目录中使用相同的不区分大小写的名称编写两个公共 Java 类,那么这两个类在运行时都不能使用.(我在 Windows、Mac 和 Linux 上使用多个版本的 HotSpot JVM 进行了测试.如果有其他 JVM 可以同时使用它们,我不会感到惊讶.)例如,如果我创建一个名为 a 和一个像这样命名的 A :

If one writes two public Java classes with the same case-insensitive name in different directories then both classes are not usable at runtime. (I tested this on Windows, Mac and Linux with several versions of the HotSpot JVM. I would not be surprised if there other JVMs where they are usable simultaneously.) For example, if I create a class named a and one named A like so:

// 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 项目是 可从我的网站获得.

Three eclipse projects containing the code above are available from my website.

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

If try I calling myCase on both classes like so:

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

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

The typechecker succeeds, but when I run the class file generate by the code directly above I get:

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

Exception in thread "main" java.lang.NoClassDefFoundError: testcase/A (wrong name: testcase/a)

在 Java 中,名称通常区分大小写.一些文件系统(例如 Windows)不区分大小写,所以我对上述行为的发生并不感到惊讶,但它似乎错误.不幸的是,Java 规范对于哪些类是可见的很奇怪.Java 语言规范 (JLS),Java SE 7 版(第 6.6.1 节,第 166 页)说:

In Java, names are in general case sensitive. Some file systems (e.g. Windows) are case insensitive, so I'm not surprised the above behavior happens, but it seems wrong. Unfortunately the Java specifications are oddly non-commital about which classes are visible. The Java Language Specification (JLS), Java SE 7 Edition (Section 6.6.1, page 166) says:

如果一个类或接口类型被声明为公共的,那么它可以被任何代码,前提是声明它的编译单元(第 7.3 节)是可观察到的.

If a class or interface type is declared public, then it may be accessed by any code, provided that the compilation unit (§7.3) in which it is declared is observable.

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

In Section 7.3, the JLS defines observability of a compilation unit in extremely vague terms:

预定义包java及其子包lang的所有编译单元和 io 始终是可观察的.对于所有其他包,主机系统确定哪些编译单元是可观察的.

All the compilation units of the predefined package java and its subpackages lang and io are always observable. For all other packages, the host system determines which compilation units are observable.

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

The Java Virtual Machine Specification is similarly vague (Section 5.3.1):

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

The following steps are used to load and thereby create the nonarray class or interface C denoted by [binary name] N using the bootstrap class loader [...] Otherwise, the Java virtual machine passes the argument N to an invocation of a method on the bootstrap class loader to search for a purported representation of C in a platform-dependent manner.

所有这些都导致了四个问题,按重要性降序排列:

All of this leads to four questions in descending order of importance:

  1. 是否可以保证每个 JVM 中的默认类加载器可以加载哪些类?换句话说,我能否实现一个有效但退化的 JVM,它不会加载除 java.lang 和 java.io 中的类之外的任何类?
  2. 如果有任何保证,上面示例中的行为是否违反了保证(即该行为是否存在错误)?
  3. 有没有办法让 HotSpot 同时加载 aA ?编写自定义类加载器有用吗?
  1. 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?
  2. If there are any guarantees, does the behavior in the example above violate the guarantee (i.e. is the behavior a bug)?
  3. Is there any way to make HotSpot load a and A simultaneously? Would writing a custom class loader work?

推荐答案

  • 是否可以保证每个 JVM 中的引导类加载器可以加载哪些类?

语言的核心部分,以及支持的实现类.不保证包含您编写的任何课程.(普通的 JVM 将您的类加载到与引导程序不同的类加载器中,事实上,普通的引导加载器通常从 JAR 中加载其类,因为这比充满类的大型旧目录结构更有效地部署.)

The core bits and pieces of the language, plus supporting implementation classes. Not guaranteed to include any class that you write. (The normal JVM loads your classes in a separate classloader from the bootstrap one, and in fact the normal bootstrap loader loads its classes out of a JAR normally, as this makes for more efficient deployment than a big old directory structure full of classes.)

  • 如果有任何保证,上面示例中的行为是否违反了保证(即该行为是否存在错误)?
  • 有没有办法让标准"JVM 同时加载 a 和 A?编写自定义类加载器有用吗?

Java 通过将类的全名映射到文件名来加载类,然后在类路径中搜索该文件名.因此 testcase.a 转到 testcase/a.class 并且 testcase.A 转到 testcase/A.class.一些文件系统将这些东西混合在一起,并且可能会在需要时为其他文件系统提供服务.其他人做对了(特别是 JAR 文件中使用的 ZIP 格式的变体是完全区分大小写和可移植的).Java 对此无能为力(尽管 IDE 可以通过将 .class 文件远离本机 FS 来为您处理它,但我不知道是否有任何实际操作和 JDK 的 javac 肯定没那么聪明).

Java loads classes by mapping the full name of the class into a filename that is then searched for on the classpath. Thus testcase.a goes to testcase/a.class and testcase.A goes to testcase/A.class. Some filesystems mix these things up, and may serve the other up when one is asked for. Others get it right (in particular, the variant of the ZIP format used in JAR files is fully case-sensitive and portable). There is nothing that Java can do about this (though an IDE could handle it for you by keeping the .class files away from the native FS, I don't know if any actually do and the JDK's javac most certainly isn't that smart).

但这并不是这里要注意的唯一一点:类文件在内部知道它们在谈论什么类.文件中缺少 expected 类仅意味着加载失败,导致您收到 NoClassDefFoundError.你得到的是一个问题(至少在某种意义上是错误部署),它被检测到并得到了强有力的处理.从理论上讲,您可以构建一个可以通过不断搜索来处理此类事情的类加载器,但是何必呢?这些都得到了正确的处理.

However that's not the only point to note here: class files know internally what class they are talking about. The absence of the expected class from the file just means that the load fails, leading to the NoClassDefFoundError you received. What you got was a problem (a mis-deployment in at least some sense) that was detected and dealt with robustly. Theoretically, you could build a classloader that could handle such things by keeping searching, but why bother? Putting the class files inside a JAR will fix things far more robustly; those are handled correctly.

更一般地说,如果您经常遇到此问题,请在具有区分大小写文件系统的 Unix 上进行生产构建(推荐使用 Jenkins 等 CI 系统)并查找哪些开发人员是仅区分大小写并让它们停止,因为这非常令人困惑!

More generally, if you're running into this problem for real a lot, take to doing production builds on a Unix with a case-sensitive filesystem (a CI system like Jenkins is recommended) and find which developers are naming classes with just case differences and make them stop as it is very confusing!

相关文章