48

如果一个人在不同的目录中使用相同的不区分大小写的名称编写两个公共 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 的声称表示。

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

  1. 是否可以保证每个 JVM 中的默认类加载器可以加载哪些类?换句话说,我能否实现一个有效但退化的 JVM,它不会加载除 java.lang 和 java.io 中的类之外的任何类?
  2. 如果有任何保证,上面示例中的行为是否违反了保证(即该行为是否是错误)?
  3. 有什么方法可以同时加载 HotSpotaA?编写自定义类加载器会起作用吗?
4

3 回答 3

19
  • 对于每个 JVM 中的引导类加载器可以加载哪些类,是否有任何保证?

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

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

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

然而,这并不是这里要注意的唯一一点:类文件在内部知道它们在谈论什么类。文件中缺少预期的类仅意味着加载失败,导致NoClassDefFoundError您收到。你得到的是一个问题(至少在某种意义上是错误部署),它被检测到并得到了强有力的处理。从理论上讲,您可以构建一个可以通过不断搜索来处理此类事情的类加载器,但是为什么要麻烦呢?将类文件放在 JAR 中会更稳健地解决问题;这些都得到了正确处理。

更一般地说,如果您经常遇到这个问题,请在 Unix 上使用区分大小写的文件系统(推荐使用像 Jenkins 这样的 CI 系统)进行生产构建,并找出哪些开发人员正在命名仅区分大小写的类并让他们停下来,因为这非常令人困惑!

于 2012-06-05T18:10:53.373 回答
1

多纳尔的精彩解释几乎没有什么可补充的,但让我简要地思考一下这句话:

...具有相同不区分大小写名称的 Java 类 ...

一般来说,名称和字符串本身永远不会不区分大小写,只有存在解释才能做到。其次,Java 不做这样的解释。

因此,对您的想法的正确表述是:

... Java 类,其文件表示在不区分大小写的文件系统中具有相同的名称 ...

于 2012-06-06T07:48:40.720 回答
-2

不要只考虑文件夹。

为您的类使用显式不同的命名空间(“包”),并且可能使用文件夹来匹配您的类。

当我提到“包”时,我不是指“*.JAR”文件,而是指以下概念:

package com.mycompany.mytool;

// "com.mycompany.mytool.MyClass"

public class MyClass
{
   // ...
} // class MyClass

当您没有为您的代码指定包时,java 工具(编译器、IDE 等)假定对所有人使用相同的全局包。而且,如果有几个类似的类,它们有一个文件夹列表,可以在哪里查找。

包就像代码中的“虚拟”文件夹,适用于类路径或 Java 安装中的所有包。您可以拥有多个具有相同 ID 的类,但是,如果它们位于不同的包中,并且您指定要查找的包,则不会有任何问题。

只需我的 2 美分,用于您的一杯 Java 咖啡

于 2012-06-05T17:36:28.337 回答