工具类规范
1.介绍
在开始写代码之前,必须明确Utils的定义
工具类是指一组静态方法的集合,用于提供通用的,独立于业务状态的功能
2.核心特征
- 无状态:工具类不应该持有业务数据或上下文状态
- 通用性:它的逻辑应该是通用的,可以在不同模块甚至不同项目中复用
- 被动性:它不主动发起业务流程,只在被调用时执行单一任务
3.绝对禁止的行为
- 禁止注入Bean:工具类不应该通过@Autowired或SpringContext获取Service或Dao。如果你的Util需要查数据库,那它就不是Util,而是Service或Component
- 禁止包含业务逻辑:例如OrderUtils.calculatePrice()如果包含了特定的促销规则,它应该属于领域模型或业务服务,而不是通用规则
4.编写规范
final类与私有构造器
介绍
这是工具类最基本的签名,使用者一眼就可以看出来这大概是一个工具类
- final修饰:明确告知开发者,这个类不允许被继承。继承工具类通常是反模式的开始
- private构造器:防止该类被实例化。工具类只有静态方法,实例化没有任何意义
示例
public final class IpUtils {
// 1.私有构造器,防止实例化
private IpUtils () {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
// 2.静态方法
public static String getCurrentIp () {
// ...
}
}
只有静态方法和常量
介绍
工具类中不应包含非静态成员
- 方法:全部为public static
- 变量:只能是public static final的常量。绝不允许定义public static的可变变量(这是线程安全噩梦的根源)
参数的纯函数特性
介绍
工具类方法应当金量接近纯函数
- 输出仅依赖于输入参数
- 执行过程中不产生副作用(不修改全局变量,不修改入参状态)
反例
// 修改了入参list,不仅不安全,还可能导致调用方出现莫名其妙的bug
public static void filterList (List<String> list) {
list.removeIf(String::isEmpt);
}
正例
// 返回一个新的List,保证输入参数不可变
public static List<Stirng> filterList (List<String> list) {
if (list == null) return new ArrayList();
return list.stream()
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList);
}
细粒度拆分:拒绝CommonUtils
介绍
这是垃圾场形成的最大诱因。永远不要创建一个CommonUtils,GeneraUtils或BaseUtils的类
解决方案
按照功能领域拆分,比如下面的拆分方式:
- 处理字符串:StringUtils
- 处理日期:DateUtils
- 处理集合:CollectionUtils
- 处理文件:FileUtils
- 处理加密:CodecUtils/DigestUtils
注意
如果一个类超过了500行,或者引入了不相关的依赖(比如StringUtils突然引入了Servlet-API),就说明需要拆分了
零依赖或轻量级依赖
介绍
工具类应当处于项目依赖树的叶子节点
- 严谨依赖业务层:Utils不能依赖Domain,Service,Dao
- 谨慎引入第三方重型库:如果你的StringUtils只是为了判断空字符串,却引入了整个Spring Context或Hadoop库,这是不可接受的
完善的空指针处理
介绍
工具类必须具备极高的健壮性,入参宽容,出参严谨
- 入参:假设所有入参都可能是null
- 出参:尽量不返回null。返回空集合,空字符串或Optional
示例
// 正例
public static String reverse (String str) {
if (str == null) {
return null;
}
return new StringBuilder(str).reverse().toString();
}
异常处理
介绍
工具类中的异常处理也要注意
- 转换异常:不要让checked exception(如IOException)污染调用方代码,建议在工具类内部捕获,并转化为RuntimeException或自定义异常抛出
- 静默处理:对于非关键性工具(如解析UserAgent),如果出错,可以打日志并返回默认值,而不是导致整个请求崩溃
5.命名规范
类命名
统一使用Utils或Helper后缀
- Utils:通常指完全静态、通用的工具
- Helper:有时用于包含特定上下文辅助逻辑的类(稍微宽容一点,但也建议用静态)
方法命名
- isXxx/hasXxx:返回boolean,如isEmpt,isEmail
- toXxx/parseXxx:类型转换,如toInt,parseDate
- formatXxx:格式化,如formatMoney
- getXxx:获取属性,如getFileExtension
- wrapXxx/unwrapXxx:包装或解包
6.避免重复造轮子
介绍
在写任何工具类之前,先问自己:Apache Commons或Guava里面有了吗?
Java生态中最顶级的工具库,已经经过了全球开发者的千万次验证,能用就不要重复造轮子
推荐引入的标准库
- Apache Commons Lang3:StringUtils,ArrayUtils,NumberUtils,Pair
- Apache Commons Collections4:CollectionUtils,MapUtils
- Apache Commons IO:FileUtils,IOUtils
- Google Guava:Lists,Maps,ImmutableList
- Hutool:国内非常流行的库,全能型工具库,适合不想引入太多细碎依赖的中小型项目
7.正确的封装
介绍
如果团队中决定统一使用org.apache.commons.lang3.StringUtils,是否需要自己在写一个StringUtils去继承他?
规范
- 不要继承:Commons的类通常也是final的,无法继承
- 可以组合/代理:如果你需要统一项目的特定逻辑(例如特殊的手机号脱敏),可以建立项目的ProjectStringUtils,里面调用Commons的方法,补充自己的方法
- 直接使用:对于标准方法(如isEmpt),可以直接调用第三方库即可,不要再包一层,徒增认知成本
8.JDK21对Utils的冲击
介绍
随着JDK21的普及,曾经必须依赖的工具类才能实现的很多功能,现在已经是JDK的原生语法。继续在工具类中封装这些功能,不仅多余,还会增加认知负担
JDK各个版本的工具类方法
省略
9.模式匹配取代类型转换工具
介绍
JDK16的instanceof模式匹配和JDK21的Switch模式匹配,消灭了大量用于类型判断+强转的工具方法
老写法
// 以前可能写一个CastUtils
User user = CastUtils.castToUser(obj);
新写法
// 原生语法直接处理,无需封装
if (obj instanceof User user) {
// 直接使用,无需强转
user.getName();
}
// 或者使用Switch模式匹配
String result = switch (obj) {
case User u -> "User:" + u.getName();
case Order o -> "Order:" + o.getId();
default -> "Unknown";
}