虽然Java编译器允许在一个源文件中定义多个顶级类,但这么做并没有什么好处,只会带来巨大的风险。因为在一个源文件中定义多个顶级类,可能导致给一个类提供多个定义。哪一个定义会被用到,取决于源文件被传给编译器的顺序。
为了更具体地说明,下面举个例子,这个源文件中只包含一个Main类,他将引用另一个外来两个顶级类(Utensil )和(Dessert)的成员:
public class Main {
public static void main(String[] args) {
System.out.println(Utensil.NAME + Dessert.NAME);
}
}
现在假设你在一个名为Utensil.java的源文件中同时定义了Utensil和Dessert:
// Two classes defined in one file. Don't ever do this!
class Utensil {
static final String NAME = "pan";
}
class Dessert {
static final String NAME = "cake";
}
当然,主程序会打印:“pancake”。
现在假设你不小心在另一个名为Dessert.java的源文件中也定义了同样的两个类:
// Two classes defined in one file. Don't ever do this!
class Utensil {
static final String NAME = "pot";
}
class Dessert {
static final String NAME = "pie";
}
如果你侥幸是用javac Main.java Dessert.java来编译程序,那么编译就会失败,此时编译器会提醒你定义了多个Utensil和Dessert类。这是因为编译器会先编译Main.java,当它看到Utensil的引用(在Dessert引用之前)。就会在Utensil.java中查看这个类,结果找到Utensil的Dessert这两个类。当编译器在命令行遇到Dessert.java时,也会去查找该文件,结果会遇到Utensil和Dessert这两个定义。
如果用命令javac Main.java或者javac Main.java Utensil.java编译程序,结果将如同你还没有编写Dessert.java文件一样,输出pancake。但如果是用命令javac Dessert.java Main.java编译程序,就会输出pancake。程序的行为受源文件被传给编译器的顺序影响,这显然是让人无法接收的。
这个问题的修正方法很简单,只要把顶层类(在本例中是指Utensil和Dessert)分别放入独立的源文件即可。如果一定要把多个顶层类放在一个源文件中,就要考虑使用静态成员类(详见第24条),以此代替将这两个类分别独立到源文件中。如果这些类服从于另一个类,那么将它们做成静态成员类通常比较好,因为这样增强了代码的可读性,如果将这些类声明为私有的(详见第15条),还可以使它们减少被读取的概率,以下就是做成静态成员类的范例:
// Static member classes instead of multiple top-level classes
public class Test {
public static void main(String[] args) {
System.out.println(Utensil.NAME + Dessert.NAME);
}
private static class Utensil {
static final String NAME = "pan";
}
private static class Dessert {
static final String NAME = "cake";
}
}
结论显而易见:永远不要把多个顶级类或者接口放在一个源文件中
。遵循这个规则可以确保编译时一个类不会有多个定义。这么做反过来也能确保编译产生的类文件,以及程序结果的行为,都不会收到源文件被传给编译器的顺序的影响。
网友评论