通过私有构造器强化不可实例化的能力
上一条讲单例的时候已经提到了私有化构造器。单例模式这么搞是为了保证全局只有一个实例,而这一条规则是为了让工具类一个实例都无法创建。
这样的例子在JDK中有很多:java.util.Collections
,java.util.Arrays
,java.util.concurrent.Executors
,java.util.Objects
,java.util.stream.Collectors
。
它们有几个共同点。它们都是以Xxxs命名的,当然这只是JDK中对工具类的命名规则, 但是在Spring等框架中更偏好用XxxUtils进行命名。这些不是重点,它们最主要的共同点是只包含静态方法和静态字段(我暂且管这种类叫“静态类”),这样做的好处是可以不用创建对象直接用类名调用静态方法。好用是好用但是在Java中“静态类”不能滥用。
工具类的最佳实践
结合我在JDK以及apache commons中看过的一些源码,一个纯粹的工具类应该这么写
1 | public final class XxxUtils { |
私有化构造器,而不是抽象类
为了让调用者无法创建工具类的实例对象,有一种做法是让工具类变成抽象类。之前在一些源码里面看到过这种做法,但是通常不提倡这样做,因为这个类可以被继承,子类仍可以实例化,而且抽象工具类可能会误导用户以为这个类是专门为了继承而设计的,相反我们应该加上final
关键字让这个工具类不能被继承,为了避免实例化应该私有化构造器,《Effective Java》中的例子为了防止构造器在类内调用,甚至在构造器中抛出错误,当然这并不是必需的,但至少在构造器上添加一条注释。
“静态类”与单例模式的区别
在很多时候这些“静态类”和单例模式非常相似:单例保证类只有一个对象,然后我们调用这个对象的方法;静态类只会有一个class,而没有类的实例,可以直接通过类名调用方法。这些时候甚至分不清到底改用静态方法类还是单例。
看了StackOverflow中的一些讨论后,发现它们的区别其实很明显。
静态方法类是以面向过程的方式编程,而单例模式是以面向对象的方式编程。
正因如此在使用单例模式的时候我们可以使用面向对象的各种特性:
我们可以让单例类继承某各类,从而拥有了父类的所有方法;
我们可以让单例类实现某个接口,从而在所有需要这个接口的地方我们可以把单例对象传进去。
总而言之:除了类似于前面的那些工具类可以用“静态类”,其他时候尽量避免全静态方法的类。