Java:一个文件中有多个类声明

293

在Java中,您可以在单个文件中定义多个顶级类,但其中最多只能有一个是public(请参见JLS §7.6)。以下是示例。

  1. 这种技术是否有一个整洁的名称(类似于innernestedanonymous)?

  2. JLS说系统可能会强制执行限制,即这些辅助类不能被包中其他编译单元中的代码引用,例如,它们不能被视为包私有。这在Java实现之间是否真的会发生变化?

例如,PublicClass.java:

package com.example.multiple;

public class PublicClass {
    PrivateImpl impl = new PrivateImpl();
}

class PrivateImpl {
    int implementationData;
}

16
好问题。我从来没有认真考虑过这个问题,因为几乎从不需要这样做。 - Michael Myers
16
请注意,这是一个退化的功能;如果Java一开始就具有嵌套类,这将永远不可能实现。 - Kevin Bourrillion
9个回答

158

Javac并没有主动禁止这样做,但它有一个限制,几乎意味着你永远都不想从另一个文件中引用顶级类,除非它与所在的文件具有相同的名称。

假设你有两个文件:Foo.javaBar.java

Foo.java包含:

  • public class Foo

Bar.java包含:

  • public class Bar
  • class Baz

还假设所有的类都在同一个包中(并且文件在同一个目录中)。

如果Foo引用了Baz但没有引用Bar,并尝试编译Foo.java会发生什么?编译将失败,并出现如下错误:

Foo.java:2: cannot find symbol
symbol  : class Baz
location: class Foo
  private Baz baz;
          ^
1 error
这是有道理的。如果Foo引用了Baz,但是没有Baz.java(或Baz.class)文件,那么javac怎么知道在哪个源代码文件中查找呢?如果你告诉javac同时编译Foo.javaBar.java,或者之前已经编译过Bar.java(保留了javac可以找到的Baz.class文件),或者Foo除了引用Baz以外还引用了Bar,则此错误将消失。但这样做会让你的构建过程感觉不太可靠和靠不住。因为实际的限制更像是“不要从另一个文件引用顶层类,除非它具有与其所在文件同名的名称,或者你还引用了另一个与该文件同名的类”,这个规则难以遵循,人们通常采用更加严格但更直接的约定,即每个文件只放置一个顶层类。如果你改变了对一个类是否应该是公共的看法,这也会更好。新版的javac也可以通过使用-Xlint:all在这种情况下产生警告。
auxiliary class Baz in ./Bar.java should not be accessed from outside its own source file

有时候每个人都采用某种特定方式确实有充分的理由。


Maven是否有任何措施来确保编译的可靠性? - Aleksandr Dubinsky
@Laurence,你说“如果Foo.java引用了Baz,但是没有Baz.java(或Baz.class),那么javac怎么知道要查找哪个源文件?”但是即使我创建了Baz.java,编译Foo.java仍然会失败。它仍然不知道要查找哪个源文件。 - Kushal Kumar
1
@KushalKumar javac 将搜索源路径,默认为用户类路径(默认为当前目录)。如果您的源不是根据类路径中的目录,则需要指定源路径。 - Laurence Gonsalves
@LaurenceGonsalves 谢谢。我读了几遍后,现在已经理解得很好了。除了你提到的实际限制是什么之外,"除非它与文件名相同或者您还引用了与文件名相同的同一文件中的类,否则不要从另一个文件引用顶级类"这句话也能解释一下吗? - Kushal Kumar
1
@KushalKumar javac 从你明确要求它编译的源文件集开始。当你引用不在这些源文件中的类时,它会检查编译后的类路径和源路径以查找具有匹配名称的源文件。如果它找到了后者,它就会将其添加到正在处理的源文件集中。因此,如果你引用了 Baz,并且它与 Bar 在同一个文件中,javac 只会找到 Baz,因为它由于对 Bar 的引用而引入了 Bar.java。 - Laurence Gonsalves
显示剩余2条评论

131

我建议这种在一个源文件中包含多个顶层类的技术称为“混乱”(mess)。但说真的,我认为这不是一个好主意 - 在这种情况下我会使用嵌套类型。这样仍然容易预测它在哪个源文件中。虽然我不认为这种方法有官方术语。

至于是否在不同的实现中有所不同,我非常怀疑,但如果你能避免一开始就这么做,你就永远不需要关心这个问题 :)


87
我并不是这个回答的踩贴者,但我认为这个回答被踩的最有可能的原因是它有点“规范化”,即使用了“应该”而不是“实际上……然而……”。它实际上并没有回答任何问题,就像引出一个无关的例外情况而不返回任何内容一样,或者提出一个与实际事实相关的异常情况而不是意见。 - n611x007
6
我发现了@JonSkeet建议使用嵌套类型的一个小例外(我原本会同意他的观点):如果主类是泛型的,而类型参数是第二个类,则第二个类不能是嵌套的。如果这两个类紧密耦合(就像问题中的PublicClass和PrivateImpl一样),那么我认为将PrivateImpl作为顶级类放在同一个文件中是一个很好的想法。 - jfritz42
6
@BoomerRogers说:“不,这绝对不是组件化编程的“核心基础”。如果你正在针对一个组件进行编程,为什么要关心源代码的组织方式?(就我个人而言,我更喜欢依赖注入而不是服务定位器模式,但那是另一回事。)将API和源代码组织分开看待-它们是非常不同的事情。” - Jon Skeet
3
让我来重新表述一下:你的“答案”是一个与问题无关的个人意见。(例如,“mess”和“i doubt it”这样的回答几乎没有价值。)因此,你的帖子没有回答任何一个提出的问题。请查看polygenelubricants的答案,你会发现他成功地回答了两个问题。 - bvdb
1
@bvdb:(有很多不良实践是规范允许的。我敦促人们不要编写 public int[] foo(int x)[] { return new int[5][5]; },即使这是有效的。) - Jon Skeet
显示剩余11条评论

25

1
我唯一的问题是,你可以有一个非公共顶级类成为文件中唯一的类,因此它并没有解决多样性问题。 - Michael Brewer-Davis
我理解您的担忧,但是正如您所看到的,这是其他人历史上使用过的术语。如果我必须自己创造一个术语,我可能会称其为“次级顶层类型”。 - polygenelubricants
这实际上是一个仅包含链接的答案,现在太阳论坛的链接已经失效了,剩下的内容不多。 - eis

18

您可以按照以下方式拥有任意数量的类

public class Fun {
    Fun() {
        System.out.println("Fun constructor");
    }
    void fun() {
        System.out.println("Fun mathod");
    }
    public static void main(String[] args) {
        Fun fu = new Fun();
        fu.fun();
        Fen fe = new Fen();
        fe.fen();
        Fin fi = new Fin();
        fi.fin();
        Fon fo = new Fon();
        fo.fon();
        Fan fa = new Fan();
        fa.fan();
        fa.run();
    }
}

class Fen {
    Fen() {
        System.out.println("fen construuctor");

    }
    void fen() {
        System.out.println("Fen method");
    }
}

class Fin {
    void fin() {
        System.out.println("Fin method");
    }
}

class Fon {
    void fon() {
        System.out.println("Fon method");
    } 
}

class Fan {
    void fan() {
        System.out.println("Fan method");
    }
    public void run() {
        System.out.println("run");
    }
}

1
@Nenotlep 当您进行“改善格式”时,请确保它不会干扰代码本身,例如删除反斜杠。 - Tom
5
这并没有回答问题。 - ᴠɪɴᴄᴇɴᴛ
1
这是对我来说最好的一个。 - Mote Zart

6

提供一下参考信息,如果您使用的是Java 11+版本,则有一个例外规则:如果您直接运行Java文件(无需编译),在此模式下,每个文件中可以有多个公共类而不受限制。然而,带有main方法的类必须是该文件中的第一个。


4

1.这种技术有一个整洁的名称吗(类似于内部,嵌套,匿名)?

多类单文件演示。

2.JLS表示,系统可能会强制执行这些辅助类不能被其他包的编译单元中的代码引用的限制,例如,它们不能被视为包私有。这真的是在Java实现之间发生变化的事情吗?

我不知道有没有不受此限制的实现 - 所有基于文件的编译器都不允许您引用文件名与类名不同的源代码类。(如果您编译了一个多类文件,并将类放在类路径上,则任何编译器都会找到它们)。


1

你可以这样做,使用外部公共类中的公共静态成员,像这样:

public class Foo {

    public static class FooChild extends Z {
        String foo;
    }

    public static class ZeeChild extends Z {

    }

}

还有另一个引用上述文件的文件:

public class Bar {

    public static void main(String[] args){

        Foo.FooChild f = new Foo.FooChild();
        System.out.println(f);

    }
}

将它们放在同一个文件夹中。使用以下命令进行编译:

javac folder/*.java

同时运行并且使用:

 java -cp folder Bar

3
那个例子并没有回答问题。你给出的是嵌套静态类的例子,这与在同一文件中定义两个顶级类不同。 - Pedro García Medina

0
根据《Effective Java》第二版(第13项):

“如果仅有一个类使用包可见的顶层类(或接口),请考虑将该顶层类作为私有嵌套类放置于使用它的唯一类中(第22项)。这样能将其从其所在包中的所有类的访问权限降低到只有使用它的那个类。但更重要的是,减少不必要公开类的访问权限要比减少包可见的顶层类的访问权限更加重要: ...”

嵌套类可以是静态或非静态的,具体取决于成员类是否需要访问封闭实例(第22项)。

1
OP并没有询问关于嵌套类的问题。 - charmoniumQ

-15

不行。但在Scala中非常可能:

class Foo {val bar = "a"}
class Bar {val foo = "b"}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接