Poison

Should Helper/Utility Classes Be Abstract?

今天看到同事写的代码中将工具类声明为 abstract,猜测是为了防止将该工具类实例化?个人认为这种方法属于使用不当,如果查看 JDK 中 Arrays 的源码:

1
2
3
4
5
6
public class Arrays {
// Suppresses default constructor, ensuring non-instantiability.
private Arrays() {}

// ...
}

可以看出,工具类 Arrays 采用了一个私有的构造函数以防止该类被实例化,同理,观察 JDK 中 Collections 的源码:

1
2
3
4
5
6
7
public class Collections {
// Suppresses default constructor, ensuring non-instantiability.
private Collections() {
}

// ...
}

实现与 Arrays 一致,也是采用的私有化构造器防止工具类被实例化,均未采用抽象类的方式去实现工具类。在 《Effective Java中文版(第3版)》 第 4 条 “通过私有构造器强化不可实例化的能力” 中也有提到,企图通过将类做成抽象类来强制该类不可被实例化,这是行不通的。因为该类可以被子类化,并且该子类也可以被实例化。这样做甚至会误导用户,以为这种类是专门为了继承而设计的。根据 Effective Java 中的描述,建议使用如下的私有构造器:

1
2
3
4
5
6
7
// Noninstantiable utility class
public class UtilityClass {
// Suppress default constructor for noninstantiability
private UtilityClass() {
throw new AssertionError();
}
}

相比 JDK 中 ArraysCollections 中的实现,在私有构造器中,还新增了对 Error 的抛出,此行代码可以避免不小心在类的内部调用构造器。它保证该类在任何情况下都不会被实例化。比如 java.util.Objects 类的源代码,即使用了这样的私有构造器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* This class consists of {@code static} utility methods for operating
* on objects. These utilities include {@code null}-safe or {@code
* null}-tolerant methods for computing the hash code of an object,
* returning a string for an object, and comparing two objects.
*
* @since 1.7
*/
public final class Objects {
private Objects() {
throw new AssertionError("No java.util.Objects instances for you!");
}

// ...
}

如果查看 org.springframework.util 中的工具类,你会发现几乎全部被声明了 abstract,个人认为这是一个典型的反例,源码参见:spring-framework/spring-core/src/main/java/org/springframework/util at v5.3.10 · spring-projects/spring-framework · GitHub,关于该问题的讨论可以参考:why “NestedExceptionUtils” in spring source code is declared as an abstract class? - Stack Overflow

Reference

Should Helper/Utility Classes be abstract? - Stack Overflow
What is the best way to write utility classes in Java? - Quora