有时候,你可能需要编写只包含静态方法和静态属性的类。这些类名声很不好,因为有些人在面向对象的语言中滥用这样的类来编写过程化的程序,但是它们确实有特有的用处。它们可被用来以 java.lang.Math 或者 java.utill.Arrays 的方式,把初始值或数组的相关方法进行组合。它们也可被用来以 java.util.Collections 的方式,把实现某些接口对象的静态方法(包括静态工厂方法(第 1 条))进行组合(自 Java 8 开始,你也可以把这些方法写进你定义的接口中)。最后,这些类还可以用来把 final 类中的方法进行组合,因为你不可能把这些方法放到一个子类中。
这样的工具类(utility class)不需要被实例化,它的实例是没有意义的。 然而,当缺乏显式构造器时,编译器会提供一个公有的、无参的默认构造器(default constructor)。对用户而言,这个构造器和其它构造器是没有任何区别的。在已发布的 API 中,经常可以看到一些被无意识地实例化的类。
企图通过把类做成抽象来强制不可实例化是行不通的。 该类可以被子类化,并且其子类可以被实例化。除此之外,它还会误导读者认为该类是为继承而设计的(第 19 条)。其实,这有一个简单的习惯用法来确保类的不可实例化。只有当一个类不包含显式构造器时,才会生成一个默认的构造器。所以通过让类包含私有构造器,可以使其不可实例化:
// 不可实例化工具类
public class UtilityClass {
// 禁止默认构造器,以实现非实例化
private UtilityClass() {
throw new AssertionError();
}
// ..
// 其余省略
}
因为显式构造器是私有的,所以在类外是不可访问的。AssertionError 并不是严格需要的,但它可以防止在类内意外调用构造器。它可以保证类在任何环境下都绝不可能被实例化。这个习惯用法有点违反直觉,因为构造器是明确提供的却是为了该构造器无法被调用,因此,如上面所示那样,添加一条注释不失为一个明了的写法。
这个习惯用法也有一些副作用,它同时也阻止了该类被子类化。因为该类中所有的构造器必须显式或隐式地调用一个超类(superclass)构造器,在这种情况下,子类就没有可以访问的超类构造器来调用了。
翻译:Inno
校对:Inger