为什么每个公共类都在一个单独的文件中?

时间:2009-08-23 14:33:15

标签: java code-organization

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

为什么Java会这样做?有任何设计考虑因素吗?

编辑(基于几个答案):

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

12 个答案:

答案 0 :(得分:56)

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

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

  1. 您对该文件命名了什么?公共课程之一?另一个名字?人们在糟糕的解决方案代码组织和文件命名约定方面存在足够的问题,以便有一个额外的问题。

  2. 此外,当您浏览文件/项目资源管理器时,不会隐藏任何内容。例如,您可以看到一个文件并向下钻取,并且共有200个类。如果您有一个类的一个文件,您可以更好地组织测试并了解解决方案的结构和复杂性。

  3. 我认为Java做对了。

答案 1 :(得分:31)

根据Java Language Specification, Third Edition

  

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

重点是我的。

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

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

答案 2 :(得分:17)

来自Thinking in Java

  

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


来自specification (7.2.6)

  

当软件包存储在文件系统(?7.2.1)中时,如果类型不是,则主机系统可以选择来强制执行编译时错误的限制如果以下任一情况属实,则在文件名称由名称加上扩展名(如.java或.jav)组成的文件中找到

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

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

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


来自“Once upon an Oak ...

  

这很明显 - 就像大多数事情一旦你知道设计原因 - 编译器必须通过所有编译单元(.java文件)进行额外的传递,以确定哪些类在哪里,并且会使编译更慢。

(注意:

  

Oak Language Specification for Oak version 0.2(后记文档): Oak 是现在通常称为Java的原始名称,本手册是Oak(即Java)最早的手册。
  有关Java起源的更多历史记录,请查看the Green ProjectJava(TM) Technology: An Early History
  )

答案 3 :(得分:6)

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

你必须记得Java语言是在90年代中期开发的,是在IDE进行代码导航和搜索之前的几天

答案 4 :(得分:3)

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

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

答案 5 :(得分:3)

这就是语言设计师决定如何做到的。我认为主要原因是优化编译器传递 - 编译器不必猜测或解析文件来定位公共类。我认为这实际上是一件好事,它使代码文件更容易找到,并迫使你远离过多的文件。我也喜欢Java如何强制您将代码文件放在与包相同的目录结构中 - 这样可以很容易地找到任何代码文件。

答案 6 :(得分:2)

在一个文件中包含多个Java顶级类在技术上是合法的。但是,这被认为是不好的做法,如果您这样做,许多Java工具(包括IDE)都不起作用。

JLS说:

  

当包存储在文件中时   系统(§7.2.1),主机系统可以   选择执行限制   如果是类型,则是编译时错误   在名称下的文件中找不到   由类型名称加上一个组成   扩展名(如.java或.jav)if   以下任何一种情况都属实:

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

请注意在JLS文本中使用可能。这表示编译器可能会将此拒绝为无效,或者可能不会。如果您尝试将Java代码构建为源代码级,那么这不是一个好的情况。因此,即使一个源文件中的多个类在您的开发平台上运行,执行此操作也是不好的做法。

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

在实践中,经验丰富的Java开发人员根本不会错过这样做。使用包,类访问修饰符和内部或嵌套类的适当组合可以更好地完成模块化和信息隐藏。

答案 7 :(得分:2)

  

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

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

答案 8 :(得分:1)

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

我抓住了我的个人C ++ utilty库的标题(你可以从here获得它),几乎所有实际上都声明类的头文件(有些只是声明自由函数)声明了多个类。我喜欢把自己想象成一个相当不错的C ++设计师(尽管这个库有点像一个地方 - 我是它唯一的用户),所以我建议至少对于C ++来说,同一个文件中的多个类是正常的甚至是良好的实践。

答案 9 :(得分:1)

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

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

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

答案 10 :(得分:1)

单个文件中可以有许多公共类。但是,每个文件的顶级类。每个文件可以有多个公共内部/嵌套类。

我认为你喜欢这个链接 - Can a java file have more than one class?

答案 11 :(得分:1)

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

  

当包存储在文件系统(第7.2.1节)中时,主机系统   可以选择强制执行编译时错误的限制   如果在由该类型组成的名称下的文件中找不到类型   name加上一个扩展名(例如.java或.jav)   以下是真的:

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

此限制意味着每个最多必须有一个这样的类型   编译单位。这种限制使Java编译器变得容易   在包中找到一个命名类。

     

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

     

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

为了获得更清晰的图片,让我们想象在同一个源文件中有两个公共类公共类A和公共类B,而A类引用了尚未编译的类B.我们正在编译(编译 - 链接 - 加载)类A现在在链接到B类编译器时将被强制检查当前包中的每个* .java文件,因为类B没有它的特定B.java文件。所以在上面的例子中,编译器找到哪个类位于哪个源文件以及主方法所在的类中有点费时。 因此,为每个源文件保留一个公共类的原因是实际上使编译过程更快,因为它可以在链接(导入语句)期间更有效地查找源文件和编译文件。我们的想法是,如果您知道类的名称,就知道每个类路径条目应该在哪里找到它,并且不需要索引。

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

您可以在Why Single Java Source File Can Not Have More Than One public class

上阅读更多内容