47

我最近开始学习 Java,发现每个 Java 类都必须在单独的文件中声明,这很奇怪。我是 C# 程序员,C# 不强制执行任何此类限制。

为什么Java会这样做?是否有任何设计考虑?

编辑(基于几个答案):

为什么 Java 现在没有在 IDE 时代消除这个限制?这不会破坏任何现有的代码(或者会吗?)。

4

11 回答 11

61

我刚刚采用了一个 C# 解决方案并做到了这一点(删除其中包含多个公共类的任何文件)并将它们分解为单个文件,这让生活变得更加轻松。

如果您在一个文件中有多个公共类,您会遇到一些问题:

  1. 你给文件起什么名字?公开课之一?另一个名字?人们对糟糕的解决方案代码组织和文件命名约定有足够的问题,还有一个额外的问题。

  2. 此外,当您浏览文件/项目资源管理器时,最好不要隐藏东西。例如,您看到一个文件并向下钻取,有 200 个类全部混合在一起。如果您有一个文件一个类,您可以更好地组织您的测试并了解解决方案的结构和复杂性。

我认为 Java 做对了。

于 2009-08-23T15:35:05.643 回答
37

根据Java 语言规范,第三版

这个限制意味着每个编译单元最多只能有一个这样的类型。这个限制使 Java 编程语言的编译器或 Java 虚拟机的实现很容易在包中找到命名类;例如,公共类型wet.sprocket.Toad 的源代码可以在目录wet/sprocket 中的文件Toad.java 中找到,相应的目标代码可以在同一目录中的文件Toad.class 中找到。

重点是我的。

似乎基本上他们想将操作系统的目录分隔符转换为命名空间的点,反之亦然。

所以是的,这是某种设计考虑。

于 2009-08-23T14:51:55.683 回答
16

来自Java 的思考

每个编译单元(文件)只能有一个公共类。
这个想法是每个编译单元都有一个由该公共类表示的公共接口。它可以拥有任意数量的支持“友好”类。如果你在一个编译单元中有多个公共类,编译器会给你一个错误信息。


规范(7.2.6)

当包存储在文件系统(?7.2.1)中,如果在由类型名称加上如果以下任一条件为真,则为扩展名(例如 .java 或 .jav)

  • 该类型由声明该类型的包的其他编译单元中的代码引用。
  • 该类型被声明为公共的(因此可以从其他包中的代码中访问)。
  • 这个限制意味着每个编译单元最多只能有一个这样的类型。
  • 这个限制使 Java 编程语言的编译器或 Java 虚拟机的实现很容易在包中找到命名类;例如,公共类型wet.sprocket.Toad 的源代码可以在目录wet/sprocket 中的文件Toad.java 中找到,相应的目标代码可以在同一目录中的文件Toad.class 中找到。

简而言之:它可能是关于查找类而不必在类路径中加载所有内容。

编辑:“可以选择”似乎留下了遵循该限制的可能性,并且“可以”的含义很可能是RFC 2119中描述的含义(即“可选”)
尽管在实践中,这是在许多平台中强制执行的并且依赖于如此多的工具和 IDE,以至于我没有看到任何“主机系统”选择强制执行该限制。


出自《橡树往事》 ……

很明显 - 就像大多数事情一样,一旦你知道设计原因 -编译器将不得不通过所有编译单元(.java 文件)来确定哪些类在哪里,这会使编译变得更慢.

(笔记:

Oak 0.2 版的Oak 语言规范(后记文档):Oak是现在通常称为 Java 的原始名称,本手册是可用于 Oak(即 Java)的最古老的手册。
有关 Java 起源的更多历史,请查看绿色项目Java(TM) 技术:早期历史

于 2009-08-25T05:46:31.903 回答
7

只是为了避免混淆,因为从开发人员的角度来看,Java 的创建考虑到了简单性。您的“主要”类是您的公共类,如果它们位于具有相同名称的文件和类包指定的目录中,则它们很容易(由人类)找到。

您必须记得 Java 语言是在 90 年代中期开发的,那时IDE 还没有让代码导航和搜索变得轻而易举

于 2009-08-23T14:35:07.053 回答
3

在一个文件中包含多个 Java 顶级类在技术上是合法的。但是,这被认为是不好的做法(在大多数情况下),如果您这样做,某些 Java 工具可能无法工作。

JLS 是这样说的:

当包存储在文件系统中时(第 7.2.1节),如果在由类型名称加上如果以下任一情况为真,则为扩展名(例如 .java 或 .jav):

  • 该类型由声明该类型的包的其他编译单元中的代码引用。
  • 该类型被声明为公共的(因此可以从其他包中的代码中访问)。

注意在 JLS 文本中使用may 。这表示编译器可能会拒绝这是无效的,也可能不会。如果您试图构建您的 Java 代码以便在源代码级别可移植,那么这不是一个好的情况。因此,即使一个源文件中的多个类在您的开发平台上工作,这样做也是不好的做法。

我的理解是,这种“拒绝许可”是一个设计决策,部分目的是为了更容易在更广泛的平台上实现 Java。如果(相反地)JLS 要求所有编译器支持包含多个类的源文件,那么在不基于文件系统的平台上实现 Java 将会存在概念性问题。

在实践中,经验丰富的 Java 开发人员根本不会错过能够做到这一点的能力。模块化和信息隐藏最好使用包、类访问修饰符和内部或嵌套类的适当组合来完成。

于 2009-08-23T14:41:48.587 回答
3

如果一个类仅由另一个类使用,则将其设为私有内部类。这样你就可以在一个文件中拥有多个类。

如果一个类被多个其他类使用,您会将这些类中的哪一个放入同一个文件中?三个都?您最终会将所有课程都放在一个文件中...

于 2009-08-23T14:54:00.157 回答
3

这就是语言设计者决定这样做的方式。我认为主要原因是优化编译器传递 - 编译器不必猜测或解析文件来定位公共类。我认为这实际上是一件好事,它使代码文件更容易找到,并迫使您避免将太多内容放入一个文件中。我也喜欢 Java 强制您将代码文件放在与包相同的目录结构中的方式——这使得查找任何代码文件变得容易。

于 2009-08-25T05:46:25.117 回答
2

在 IDE 时代,为什么 java 现在没有消除这个限制?这不会破坏任何现有的代码(或者会吗?)。

现在所有代码都是统一的。当您看到源文件时,您就知道会发生什么。每个项目都是一样的。如果 Java 要删除此约定,您必须为您从事的每个项目重新学习代码结构,而现在您只需学习一次并在任何地方应用它。我们不应该相信 IDE 的一切。

于 2009-08-23T15:55:27.490 回答
1

不是这个问题的真正答案,而是一个数据点。

我抓取了我的个人 C++ 实用程序库的头文件(您可以从这里自己获取),几乎所有实际声明类的头文件(有些只是声明自由函数)都声明了多个类。我喜欢认为自己是一个非常优秀的 C++ 设计师(尽管这个库在某些地方有点笨拙——我是它唯一的用户),所以我建议至少对于 C++,同一个文件中的多个类是正常的甚至是好的做法。

于 2009-08-23T17:05:59.907 回答
1

它允许从 Foobar.class 到 Foobar.java 的更简单的启发式方法。

如果 Foobar 可以在任何 Java 文件中,则您有一个映射问题,这可能最终意味着您必须对所有 Java 文件进行全面扫描才能找到该类的定义。

就我个人而言,我发现这是一种奇怪的规则,它们结合起来导致 Java 应用程序可以变得非常大并且仍然很坚固。

于 2009-08-24T10:59:51.403 回答
1

好吧,实际上它是根据 Java 语言规范(第 7.6 节,第 209 页)的可选限制,但随后是 Oracle Java 编译器作为强制限制。根据 Java 语言规范,

当包存储在文件系统中时(第 7.2.1 节),如果在由类型名称加上如果以下任一情况为真,则为扩展名(例如 .java 或 .jav):

  • 该类型由声明该类型的包的其他编译单元中的代码引用。
  • 该类型被声明为公共的(因此可以从其他包中的代码中访问)。

这个限制意味着每个编译单元最多只能有一个这样的类型。此限制使 Java 编译器可以轻松地在包中找到命名类。

在实践中,许多程序员选择将每个类或接口类型放在自己的编译单元中,无论它是公共的还是被其他编译单元中的代码引用。

例如,公共类型 wet.sprocket.Toad 的源代码可以在目录 wet/sprocket 中的文件 Toad.java 中找到,相应的目标代码可以在同一目录中的文件 Toad.class 中找到。

为了更清楚地了解,让我们假设在同一个源文件中有两个公共类公共类 A 和公共类 B,并且 A 类引用了尚未编译的类 B。我们正在编译(编译-链接-加载)类 A现在,在链接到 B 类编译器时,将被迫检查当前包中的每个 *.java 文件,因为 B 类没有它特定的 B.java 文件。因此,在上述情况下,编译器需要花费一些时间来查找哪个类位于哪个源文件下以及 main 方法位于哪个类中。因此,每个源文件保留一个公共类的原因实际上是为了加快编译过程,因为它可以在链接期间更有效地查找源文件和编译文件(导入语句)。这个想法是,如果你知道一个类的名称,

而且,当我们执行我们的应用程序时,JVM 默认会查找公共类(因为没有限制并且可以从任何地方访问),并且还会public static void main(String args[])在该公共类中查找。公共类充当 Java 应用程序(程序)的 JVM 实例开始的初始类。因此,当我们在程序中提供多个公共类时,编译器本身会通过抛出错误来阻止您。这是因为以后我们不能混淆 JVM 哪个类是它的初始类,因为只有一个公共类public static void main(String args[])是 JVM 的初始类。

你可以阅读更多关于为什么单个 Java 源文件不能有多个公共类

于 2017-12-04T05:01:30.780 回答