跳到主要内容

如何编写工具类的代码

在开发过程中我们经常需要开发一些工具类,来简化我们的开发。如ID生成、日期处理等。工具类的编码需要遵循一定的规范,具体如下:

important
  1. 不可被实例化
  2. 所有方法均为静态方法
  3. 不可被继承
  4. 工具类的命名一般以 Utils 结尾 (建议)

以上 3 个原则,至少要遵循前两个,即 1 和 2。一些开源框架中的工具类都遵循 1 和 2,或者全部遵循。

不可被实例化

在Java中,工具类通常被设计为不能被实例化。原因是为了避免实例化工具类,因为工具类一般所有方法均为静态方法,是完全没有必要实例化之后再用的。即便有的工具类中有非静态方法,也应该不能被实例化,而是提供一个方法用户获取其实例,确保此工具类在系统中是单例的。

以下是几种实现:

1. 使用 abstract 关键字

使用 abstract 可以将工具类定义为一个抽象类,以此来阻止工具类的实例化。在 Spring 框架中常用这种方式。但是这种方式无法阻止类被继承。

package org.springframework.beans;

public abstract class BeanUtils {
}
java

2. 使用私有构造函数

建议使用私有构造函数来阻止类被实例化。如果类中全部是静态方法,使用私有函数可以阻止类被实例化。而如果类中是非静态方法,使用私有函数,再提供一个获取类的方法,可以确保工具类的在整个应用中只有一个实例存在。

以下是 org.apache.commons.collections4.CollectionUtils 中通过私有构造函数阻止类的实例化的代码:

package org.apache.commons.collections4;

public class CollectionUtils {

/**
* CollectionUtils should not normally be instantiated.
*/
private CollectionUtils() {}
}
java

再如:我们将系统中的配置放到了 sys.properties 文件中,然后提供了一个工具类 SysConfig 来获取系统的配置。这时候我们可以将此类设置为非静态方法的工具类。如下:

@Slf4j
public class SysConfig {

/**
* 单例
*/
private static final SysConfig INSTANCE = new SysConfig();

private final Properties properties;

/**
* 私有构造函数,阻止类的实例化,类的实例必须通过 getInstance 方法获取
*/
private SysConfig() {
this.properties = new Properties();
try {
ClassPathResource classPathResource = new ClassPathResource("sys.properties");
properties.load(classPathResource.getInputStream());
} catch (IOException e) {
log.error("配置文件加载失败", e);
}
}

public static SysConfig getInstance() {
return INSTANCE;
}

public String get(String key) {
return this.properties.getProperty(key);
}

// 测试工具类
public static void main(String[] args) {
System.out.println(SysConfig.getInstance().get("a"));
}
}
java

这样做的好处是,如果有多个配置文件,可以在此工具类中对配置文件的实例进行缓存。当只有一个配置文件时,通过静态代码块来实现初始化也是可行的,如下:

@Slf4j
public class SysConfig2 {
private static Properties properties = new Properties();

// 在静态代码块中初始化 Properties
static {
ClassPathResource classPathResource = new ClassPathResource("sys.properties");
try {
properties.load(classPathResource.getInputStream());
} catch (IOException e) {
log.error("配置文件加载失败", e);
}
}

private SysConfig2() {}

public static String get(String key) {
return properties.getProperty(key);
}

public static void main(String[] args) {
System.out.println(SysConfig2.get("a"));
}
}
java

所有方法均为静态方法

这里建议工具类内所有方法均为静态方法,如非必要,一般写非静态方法的工具类,但是切忌工具类中既有静态方法又有非静态方法。

不可被继承

不建议通过继承的方式来扩展工具类的功能。静态方法可以被继承,但是无法被重写。这一点是需要尤为注意的。

public class A {

protected A() {

}

public static void hello() {
System.out.println("A");
}
}
public class B extends A {

protected B() {
}

public static void hello() {
System.out.println("B");
}

public static void main(String[] args) {
B.hello();
}
}

// 输出:B
java

当工具类中只有一个私有的构造函数,这时候这个工具类是不能被继承的。如下:

public class A {

private A() {
}
}

public class B extends A {
}
java

这时候会报一个编译错误 There is no parameterless constructor available in 'config.A'

还有一个阻止类继承方法是使用 final 关键字,此关键字与 abstract 冲突,不可联用。使用 final 关键字可以显式的标明此类不可被继承。

如果我们的工具类内只有一个私有构造函数,那这个工具类就是无法继承的。这也是为什么建议使用 私有构造函数 来阻止类被实例化,因为使用 私有构造函数 即可阻止类被实例化,又可阻止类被继承,一箭双雕。

总结

建议的方式是

  • 工具类内全部为静态方法
  • 使用私有构造函数来阻止类被实例化和被继承。

以下是一个范例

public class LocaDateTimeUtils {

private LocaDateTimeUtils() {}

public static LocalDateTime of(String dateStr) {
// ...
}

public static String format(LocalDateTime dateTime) {
// ...
}
}
java