第25条:将源文件限制为单个顶级类

While the Java compiler lets you define multiple top-level classes in a single source file, there are no benefits associated with doing so, and there are significant risks. The risks stem from the fact that defining multiple top-level classes in a source file makes it possible to provide multiple definitions for a class. Which definition gets used is affected by the order in which the source files are passed to the compiler.

虽然Java编译器能让你在一个源文件里定义多个顶级类,但这么做并没有什么好处,相反,会有一些严重的风险。这些风险都源于这么一个事实:在一个源文件里定义多个顶级类使得为类提供多个定义成为可能。使用哪个定义将会受源文件传递给编译器的顺序的影响。

To make this concrete, consider this source file, which contains only a Main class that refers to members of two other top-level classes (Utensil and Dessert):

为了更具体地说明这一点,考虑下面这个源文件,它只包含了一个Main类,这个类指向了另外两个顶级类(Utensil和Dessert)的成员:

public class Main {
    public static void main(String[] args) {
        System.out.println(Utensil.NAME + Dessert.NAME); 
    }
}

Now suppose you define both Utensil and Dessert in a single source file named Utensil.java:

假设现在你在一个叫Utensil.java的源文件里定义了Utensil类和Dessert类:

class Utensil {
    static final String NAME = "pan"; 
}
class Dessert {
    static final String NAME = "cake";
}

Of course the main program prints pancake.

当然,主程序会打印出“pancake”。

Now suppose you accidentally make another source file named Dessert.java that defines the same two classes:

现在假设你不小心在另一个叫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";
}

If you’re lucky enough to compile the program with the command javac Main.java Dessert.java, the compilation will fail, and the compiler will tell you that you’ve multiply defined the classes Utensil and Dessert. This is so because the compiler will first compile Main.java, and when it sees the reference to Utensil(which precedes the reference to Dessert), it will look in Utensil.java for this class and find both Utensil and Dessert. When the compiler encounters Dessert.java on the command line, it will pull in that file too, causing it to encounter both definitions of Utensil and Dessert.

如果你很幸运地在编译程序时用了命令“javac Main.java Dessert.java”,那么编译将会失败,而且编译器会告诉你多次定义了Utensil类和Dessert类。这是因为,编译器第一次编译Main.java时,当它看见指向Utensil类的引用(它在Dessert类引用之前)时,它将会在Utensil.java文件里查找这个类,然后同时找到了Utensil类和Dessert类。当编译器在命令行遇到Dessert.java时,它也将会在Dessert.java里查找这个类,导致它遇到了重复的Utensil类定义和Dessert类定义。

If you compile the program with the command javac Main.java or javac Main.java Utensil.java, it will behave as it did before you wrote the Dessert.java file, printing pancake. But if you compile the program with the command javac Dessert.java Main.java, it will print potpie. The behavior of the program is thus affected by the order in which the source files are passed to the compiler, which is clearly unacceptable.

如果你用命令“javac Main.java”或“javac Main.java Utensil.java”来编译程序,它的行为将与你写Dessert.java文件之前的行为一样,打印出“pancake”。但如果用命令“javac Dessert.java Main.java”来编译程序,它将会打印出“potpie”。程序的行为受传递给编译器的源文件顺序的影响,很明显,这是无法接受的。

Fixing the problem is as simple as splitting the top-level classes (Utensil and Dessert, in the case of our example) into separate source files. If you are tempted to put multiple top-level classes into a single source file, consider using static member classes (Item 24) as an alternative to splitting the classes into separate source files. If the classes are subservient to another class, making them into static member classes is generally the better alternative because it enhances readability and makes it possible to reduce the accessibility of the classes by declaring them private (Item 15). Here is how our example looks with static member classes:

修复这个问题的办法很简单,那就是将这些顶级类(在我们的例子中时Utensil类和Dessert类)分别写到各自的源文件里去。如果你尝试将多个顶级类放入同一个源文件,可以考虑使用静态成员类(条目)作为将不同类拆分为单独源文件的替代办法。如果某些类是为其它类服务的,那么将这些类做成静态成员类通常是个更好的选择,因为它加强了可阅读性而且通过将它们声明为私有能减少类的可访问性(条目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";
    } 
}

The lesson is clear: Never put multiple top-level classes or interfaces in a single source file. Following this rule guarantees that you can’t have multiple definitions for a single class at compile time. This in turn guarantees that the class files generated by compilation, and the behavior of the resulting program, are independent of the order in which the source files are passed to the compiler.

结论很明显:永远不要将多个顶级类或接口放到一个源文件里。遵守这条规则就能保证在编译时不会遇到一个类有多个定义的情况。这又保证了编译产生的class文件和随之产生的程序行为不会依赖于传给编译器的源文件顺序。

results matching ""

    No results matching ""