虽然Java编译器允许你在单个源文件中定义多个顶级类,但是这样做没有任何好处,并存在重大风险。风险来源于这样的事实:在一个源文件中定义多个顶级类可以为类提供多个定义。使用哪个定义受源文件传递给编辑器的顺序的影响。
为了使之更具体,考虑这个源文件,它只包含一个引用其他两个顶级类(Utensil和Dessert)承运的Main类:
现在假定你在命名为Utensil.java的单一源文件中定义Utensil和Dessert:
当然这个主程序会打印“pancake”。
现在假定你不小心创建了另一个名为Dessert.java的源文件,该文件定义了相同的两个类:
如果你足够幸运使用命令javac Main.java Dessert.java来编译程序,编译将失败,编辑器将告诉你你已经多次定义Utensil和Dessert类。这是因为编译器会首先编译Main.java,当它看到对Utensil的引用时(在引用Dessert之前),它将在Utensil.java中找到此类并找到Utensil和Dessert.当编译器在命令行遇到Dessert.java时,它也会拉入该文件,导致它遇到Utensil和Dessert的两个定义。
如果你使用命令javac Main.java或java Main.java Utensil.java编译程序,它将像编写Dessert.java文件之前一样运行,打印"pancake"。但是如果使用命令javac Dessert.java Main.java编译程序,它将打印potpie。程序的行为受源文件传递给编辑器的顺序的影响,这显然是不可接受的。
修复问题就像顶级类(在我们的例子中指的是Utensil和Dessert)拆分为单独的源文件一样简单。如果你希望在单个源文件中放入多个顶级类,考虑使用静态成员类( item24)作为将类拆分为单独源文件的替代方法。如果类从属于另一个类,将它们变为静态承运类通常是更好的选择,因为它增强了可读性,并且通过将它们声明为私有来减少类的可访问性(item15)。如下是示例作为静态成员类中的样子:
这个教训很清楚:永远不要将多个顶级类或接口放在一个单独的源文件下。遵循这个规则能保证在编译时不能对单个类有多个定义。这反过来能保证编译生成的类文件和结果程序的行为与源文件传递给编译器的顺序无关。
本文写于2019.4.28,历时30天