ITEM 25: LIMIT SOURCE FILES TO A SINGLE TOP-LEVEL CLASS
虽然Java编译器允许您在一个源文件中定义多个顶级类,但是这样做没有任何好处,而且存在很大的风险。风险源于这样一个事实:在源文件中定义多个顶级类使得为一个类提供多个定义成为可能。使用哪个定义受源文件传递给编译器的顺序影响。
要使其具体化,请考虑这个源文件,它只包含一个引用其他两个顶级类成员的主类:
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 命令编译该程序,它会打印 “potpie”。因此,程序的行为受到源文件传递给编译器的顺序的影响,这显然是不可接受的。
修复这个问题非常简单,只需将顶级类(在我们的示例中是 Utensil 和 Dessert )拆分为单独的源文件。如果您想将多个顶级类放入单个源文件中,请考虑使用静态成员类(item 24)作为将类拆分为单独源文件的替代方法。如果类是另一个类的从属类,那么将它们转换为静态成员类通常是更好的选择,因为它增强了可读性,并且可以通过声明类为私有来降低类的可访问性(item 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";
}
}
教训很明显:永远不要将多个顶级类或接口放在一个源文件中。遵循这条规则可以确保您不能在编译时为单个类定义多个定义。这反过来又保证编译生成的类文件和生成程序的行为独立于源文件传递给编译器的顺序。
网友评论