Java 常用工具类深度盘点:从 JDK、Apache Commons 到 Guava、Hutool、Spring、MyBatis

欢迎你来读这篇博客。本文不是一篇“工具类 API 字典”,而是从真实 Java 后端开发的角度,系统梳理 Java
项目里常见工具类的使用边界、选型逻辑、常见坑位和团队规范。

序言:工具类的本质,不是少写几行代码

Java 工具类的存在,表面上是为了“少写重复代码”,但更深层的意义是统一语义。

比如判断字符串是否为空:

1
str == null || str.length() == 0

这当然能写,但问题是,团队里有人认为 " " 是空,有人认为只有 "" 是空;有人写 trim(),有人写 strip();有人遇到 null
直接 NPE,有人悄悄吞掉。时间久了,代码里就会出现大量“看起来差不多,语义不一样”的判断。

工具类解决的不是代码长度,而是:

  1. 统一空值语义;
  2. 统一集合语义;
  3. 统一 IO 关闭、编码、异常处理方式;
  4. 统一对象转换、Bean 拷贝、反射访问方式;
  5. 降低底层 API 的使用成本;
  6. 把易错细节封装在成熟库里。

但是,工具类也不是越多越好。一个项目同时引入 Apache Commons、Guava、Hutool、Spring、Jodd,再加上自己封装的 XXXUtil
,最后很容易变成“工具类动物园”。代码表面很优雅,实际依赖混乱、语义冲突、升级困难。

所以本文的核心观点是:

工具类的最佳实践,不是“看到好用就引”,而是建立一套稳定的优先级:JDK 优先,框架已有优先,第三方按场景引入,业务语义自己封装。


一、工具类选型的总原则

在进入具体库之前,先给出一个项目级的选择顺序。

1. JDK 能解决的,优先使用 JDK

JDK 从 Java 8 之后已经补齐了很多能力:

  • Objects
  • Optional
  • Collections
  • Comparator
  • Stream
  • Files
  • Path
  • Base64
  • java.time
  • HttpClient
  • Pattern
  • StringJoiner
  • UUID
  • CompletableFuture

如果只是简单的非空判断、集合不可变包装、文件读写、日期计算、Base64 编解码,没有必要第一时间引第三方库。

例如:

1
2
3
4
5
6
7
8
Objects.requireNonNull(userId, "userId must not be null");

List<String> names = List.of("Tom", "Jerry");

String content = Files.readString(path, StandardCharsets.UTF_8);

String token = Base64.getUrlEncoder().withoutPadding()
.encodeToString(bytes);

JDK 的优势是稳定、无额外依赖、长期兼容。它的问题是 API 有时偏底层,部分场景写起来不够顺手。

2. 项目已经使用 Spring,就优先使用 Spring 工具类

Spring 项目里天然会有:

  • org.springframework.util.StringUtils
  • CollectionUtils
  • ObjectUtils
  • Assert
  • ReflectionUtils
  • ClassUtils
  • ResourceUtils
  • FileCopyUtils
  • StreamUtils
  • BeanUtils
  • LinkedMultiValueMap
  • UriComponentsBuilder
  • StopWatch

这些工具类不一定比 Apache Commons 更强,但它们和 Spring 的资源体系、类型转换体系、Bean 体系、Web 体系结合更自然。

例如在 Spring Web 项目中构造 URI:

1
2
3
4
5
6
URI uri = UriComponentsBuilder
.fromUriString("https://api.example.com/orders")
.queryParam("page", 1)
.queryParam("size", 20)
.build()
.toUri();

这比手写字符串拼接靠谱得多,也比普通字符串工具类更贴近 Web 场景。

3. Apache Commons 适合做稳定、细粒度、可控的基础增强

Apache Commons 的特点是模块拆分清晰:

  • commons-lang3:语言基础增强;
  • commons-collections4:集合增强;
  • commons-io:IO 和文件增强;
  • commons-text:文本处理增强;
  • commons-codec:编码摘要增强;
  • commons-beanutils:JavaBean 反射增强。

它适合企业级项目长期使用,因为功能稳定、模块独立、侵入性低。

4. Guava 适合需要不可变集合、特殊集合、本地缓存、Preconditions 的项目

Guava 是 Google 的 Java 核心库,特点是“设计感强”,不是简单的工具方法堆砌。

它最值得用的部分是:

  • ImmutableList / ImmutableMap / ImmutableSet
  • Multimap
  • Multiset
  • BiMap
  • Table
  • Preconditions
  • CacheBuilder
  • RateLimiter
  • Splitter
  • Joiner
  • CharMatcher

Guava 的代码风格和 JDK 很接近,适合对代码质量、不可变对象、集合建模有要求的项目。

5. Hutool 适合中文 Java 生态里的快速业务开发

Hutool 的定位是“小而全”。它把大量常见能力都封装成 XXXUtil

  • StrUtil
  • ObjectUtil
  • CollUtil
  • MapUtil
  • BeanUtil
  • DateUtil
  • FileUtil
  • IoUtil
  • Convert
  • JSONUtil
  • HttpUtil
  • SecureUtil
  • ReflectUtil
  • ClassUtil
  • IdUtil
  • Validator

它的优势是上手极快,文档中文友好,业务开发效率很高。缺点也明显:如果直接引 hutool-all,能力太全,依赖边界容易变宽;在基础框架层或中间件层,最好按模块引入。

6. Jodd 适合偏轻量、偏独立工具的项目

Jodd 相比 Hutool 更小众,但设计上强调轻量、少依赖。常见模块包括:

  • jodd-util
  • jodd-bean
  • jodd-http
  • jodd-json
  • jodd-props

它适合对依赖体积敏感、不想引入大框架,但又希望有更好用工具类的项目。

7. Tika 不是普通工具类,而是文档解析工具箱

Apache Tika 主要用于:

  • 文件类型识别;
  • PDF、Word、Excel、PPT 等文档文本抽取;
  • 元数据抽取;
  • 搜索引擎索引前处理;
  • 内容分析。

它不是“随手引一个工具类”的库,而是文档解析能力组件。Tika 很强,但依赖可能较重,解析不可信文件时还要考虑安全、超时、内存和隔离。


二、Maven 依赖建议

下面给一个常见依赖模板,版本建议交给 Spring Boot Dependency Management、公司 BOM 或统一依赖管理控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!-- Apache Commons Lang -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>

<!-- Apache Commons Collections -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>

<!-- Apache Commons IO -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>

<!-- Apache Commons Text -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
</dependency>

<!-- Apache Tika Core -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
</dependency>

<!-- Apache Tika Parsers,按需引入 -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parsers-standard-package</artifactId>
</dependency>

<!-- Guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>

<!-- Hutool:业务项目可以用 hutool-all,基础组件建议按模块引入 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>

<!-- Jodd Util -->
<dependency>
<groupId>org.jodd</groupId>
<artifactId>jodd-util</artifactId>
</dependency>

如果你在写基础框架、starter、中间件,建议不要轻易引 hutool-all、Tika parsers 全家桶、Guava 全局暴露类型。业务系统可以更务实,底层框架要更克制。


三、字符串工具类:最常用,也最容易语义混乱

字符串工具类是 Java 项目里使用频率最高的一类。

1. JDK 原生字符串能力

JDK 自带能力包括:

1
2
3
4
5
6
7
8
9
10
11
String.isBlank();
String.isEmpty();
String.strip();
String.trim();
String.join();
String.format();
String.repeat();
String.contains();
String.startsWith();
String.endsWith();
String.replace();

Java 11 以后,String.isBlank()strip() 已经很好用。

1
2
3
4
String value = "  ";

System.out.println(value.isEmpty()); // false
System.out.println(value.isBlank()); // true

区别很关键:

  • isEmpty() 只判断长度是否为 0;
  • isBlank() 判断是否为空白字符串;
  • trim() 处理传统空白;
  • strip() 基于 Unicode 空白处理,更现代。

如果项目是 Java 11+,很多简单字符串判断可以直接用 JDK。

2. Apache Commons Lang:StringUtils

StringUtilscommons-lang3 里最常用的工具类。

常见方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
StringUtils.isBlank(str);
StringUtils.isNotBlank(str);
StringUtils.isEmpty(str);
StringUtils.isNotEmpty(str);
StringUtils.defaultIfBlank(str, "default");
StringUtils.defaultString(str);
StringUtils.equals(a, b);
StringUtils.equalsIgnoreCase(a, b);
StringUtils.containsIgnoreCase(str, "abc");
StringUtils.substringBefore(str, ":");
StringUtils.substringAfter(str, ":");
StringUtils.leftPad("7", 3, "0");
StringUtils.join(list, ",");

它的核心特点是:对 null 更宽容。

1
2
3
4
5
6
7
StringUtils.isBlank(null);  // true
StringUtils.isBlank(""); // true
StringUtils.isBlank(" "); // true

StringUtils.isEmpty(null); // true
StringUtils.isEmpty(""); // true
StringUtils.isEmpty(" "); // false

在业务代码中,isBlank 往往比 isEmpty 更符合输入校验语义。

例如:

1
2
3
4
5
public void createUser(String username) {
if (StringUtils.isBlank(username)) {
throw new IllegalArgumentException("username must not be blank");
}
}

StringUtils 的适用场景

适合:

  • 参数校验;
  • 表单输入处理;
  • 字符串截取;
  • 默认值处理;
  • null-safe 比较;
  • 字符串补齐;
  • 简单格式转换。

不适合:

  • 复杂模板渲染;
  • 正则密集处理;
  • 国际化文本;
  • 富文本处理;
  • SQL 拼接。

工具类不是魔法棒,StringUtils 再强,也别拿它拼 SQL。那不是工具类的问题,是代码开始“玄学炼丹”了。

3. Apache Commons Text:StringSubstitutor

commons-text 更偏文本处理。最常见的是 StringSubstitutor

1
2
3
4
5
6
7
8
9
10
11
Map<String, String> values = Map.of(
"name", "Mario",
"env", "prod"
);

String template = "Hello ${name}, current env is ${env}.";

String result = StringSubstitutor.replace(template, values);

System.out.println(result);
// Hello Mario, current env is prod.

这非常适合:

  • 消息模板;
  • 配置模板;
  • 简单文本替换;
  • 日志文案生成;
  • SQL 之外的非危险模板。

也支持默认值:

1
2
String template = "Hello ${name:-unknown}";
String result = StringSubstitutor.replace(template, Map.of());

StringSubstitutor 的安全边界

不要把不可信用户输入直接交给复杂插值功能,尤其是涉及系统属性、环境变量、文件、URL、XML 等 lookup 的场景。

安全建议:

1
2
3
4
5
Map<String, String> values = Map.of("name", userName);
StringSubstitutor substitutor = new StringSubstitutor(values);

// 只做 Map 变量替换,不使用 createInterpolator()
String result = substitutor.replace("Hello ${name}");

更简单地说:

业务模板用 Map 替换;不可信模板不要乱开高级 lookup。

4. Spring StringUtils

Spring 的 StringUtils 和 Apache 的 StringUtils 名字一样,但语义不同。

常用方法:

1
2
3
4
5
StringUtils.hasText(str);
StringUtils.hasLength(str);
StringUtils.trimWhitespace(str);
StringUtils.commaDelimitedListToStringArray(str);
StringUtils.collectionToCommaDelimitedString(list);

Spring 常用的是:

1
StringUtils.hasText(str)

它表示字符串不为 null,长度大于 0,并且至少包含一个非空白字符。

在 Spring 项目里做框架层判断时,用它很自然:

1
2
3
if (!StringUtils.hasText(beanName)) {
throw new IllegalArgumentException("beanName must have text");
}

但是如果业务代码里已经统一用 Apache StringUtils.isBlank,就不要混着用,否则读代码的人会一直在心里翻译:“这俩到底哪个判断空格?”

5. Hutool StrUtil

Hutool 的 StrUtil 非常好用,尤其在中文业务项目里。

常见方法:

1
2
3
4
5
6
7
8
9
StrUtil.isBlank(str);
StrUtil.isNotBlank(str);
StrUtil.emptyToDefault(str, "default");
StrUtil.blankToDefault(str, "default");
StrUtil.format("hello {}", "world");
StrUtil.split(str, ',');
StrUtil.join(",", list);
StrUtil.subBefore(str, ":", false);
StrUtil.subAfter(str, ":", false);

StrUtil.format 很适合轻量字符串格式化:

1
String msg = StrUtil.format("用户 {} 创建订单 {}", userId, orderNo);

相比 String.format,它更简单;相比日志占位符,它能直接返回字符串。

6. Guava Joiner / Splitter

Guava 的 JoinerSplitter 设计非常优雅。

1
2
3
4
5
String result = Joiner.on(",")
.skipNulls()
.join("a", null, "b");

// a,b
1
2
3
4
5
6
List<String> list = Splitter.on(",")
.trimResults()
.omitEmptyStrings()
.splitToList("a, b, , c");

// [a, b, c]

Guava 这里的优势是“链式配置语义明确”。

对比一下普通写法:

1
2
3
4
Arrays.stream(str.split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
.toList();

也不是不能写,但 Guava 的语义更直观。


四、对象与空值工具类:别把 Optional 用成新型 null

1. JDK Objects

Objects 是最值得优先使用的 JDK 工具类之一。

常用方法:

1
2
3
4
5
6
7
Objects.requireNonNull(obj);
Objects.requireNonNull(obj, "obj must not be null");
Objects.equals(a, b);
Objects.hash(a, b, c);
Objects.toString(obj, "");
Objects.nonNull(obj);
Objects.isNull(obj);

典型用法:

1
2
3
4
5
6
public OrderService(OrderRepository orderRepository) {
this.orderRepository = Objects.requireNonNull(
orderRepository,
"orderRepository must not be null"
);
}

在 Stream 里过滤 null:

1
2
3
List<String> result = list.stream()
.filter(Objects::nonNull)
.toList();

2. Apache ObjectUtils

commons-lang3ObjectUtils 提供更多 null-safe 工具。

1
2
3
4
5
6
ObjectUtils.defaultIfNull(value, defaultValue);
ObjectUtils.firstNonNull(a, b, c);
ObjectUtils.anyNull(a, b, c);
ObjectUtils.allNotNull(a, b, c);
ObjectUtils.isEmpty(obj);
ObjectUtils.isNotEmpty(obj);

比如多个候选值取第一个非空:

1
2
3
4
5
6
String name = ObjectUtils.firstNonNull(
user.getNickName(),
user.getRealName(),
user.getUsername(),
"unknown"
);

注意:ObjectUtils.isEmpty 虽然方便,但语义比较宽,会同时处理字符串、数组、集合、Map、Optional 等。团队里如果想要语义更明确,建议不要滥用它。

3. Spring ObjectUtils

Spring 的 ObjectUtils 常见于框架层。

1
2
3
4
ObjectUtils.isEmpty(obj);
ObjectUtils.nullSafeEquals(a, b);
ObjectUtils.nullSafeHashCode(obj);
ObjectUtils.containsElement(array, element);

Spring 的 ObjectUtils.isEmpty 也会判断数组、集合、Map、Optional 等。它适合 Spring 框架上下文,但业务代码里不要和
Apache、Hutool 混用。

4. Guava Preconditions

Guava 的 Preconditions 非常适合参数校验。

1
2
3
checkNotNull(userId, "userId must not be null");
checkArgument(amount.compareTo(BigDecimal.ZERO) > 0, "amount must be positive");
checkState(order.canPay(), "order can not pay");

通常建议静态导入:

1
2
3
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

语义区别:

1
2
3
checkNotNull(obj);   // 参数不能为 null
checkArgument(expr); // 方法参数非法
checkState(expr); // 对象状态非法

例如:

1
2
3
4
5
6
7
8
public void pay(Order order, BigDecimal amount) {
checkNotNull(order, "order must not be null");
checkNotNull(amount, "amount must not be null");
checkArgument(amount.compareTo(BigDecimal.ZERO) > 0, "amount must be positive");
checkState(order.canPay(), "order status does not allow payment");

order.pay(amount);
}

Preconditions 的好处是语义极其清晰。坏处是项目如果没用 Guava,为了几个校验方法单独引它就没必要。

5. Spring Assert

Spring 项目里也可以用 Assert

1
2
3
4
Assert.notNull(userId, "userId must not be null");
Assert.hasText(username, "username must have text");
Assert.notEmpty(list, "list must not be empty");
Assert.isTrue(amount.compareTo(BigDecimal.ZERO) > 0, "amount must be positive");

Spring Assert 常见于框架代码、组件初始化、Bean 参数校验。业务校验不建议全部用 Assert,因为它抛出的是运行时异常,错误码、国际化、前端提示都不好统一。

业务场景更建议:

1
2
3
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new BizException(ErrorCode.INVALID_AMOUNT);
}

五、集合工具类:从“能用”到“表达建模”

集合工具类是 Java 后端绕不开的主题。

1. JDK Collections

JDK 的 Collections 提供基础集合算法和包装。

常见方法:

1
2
3
4
5
6
7
8
9
10
Collections.emptyList();
Collections.emptyMap();
Collections.singletonList(value);
Collections.unmodifiableList(list);
Collections.sort(list);
Collections.reverse(list);
Collections.shuffle(list);
Collections.binarySearch(list, key);
Collections.frequency(list, value);
Collections.disjoint(list1, list2);

例如:

1
2
3
4
public List<Order> listOrders(Long userId) {
List<Order> orders = orderRepository.findByUserId(userId);
return orders == null ? Collections.emptyList() : orders;
}

不过 Java 9+ 以后,更推荐:

1
2
List<String> names = List.of("a", "b");
Map<String, Integer> scoreMap = Map.of("Tom", 90, "Jerry", 95);

2. Apache Commons Collections4

commons-collections4 是对 JDK 集合体系的增强。

常用类:

  • CollectionUtils
  • ListUtils
  • SetUtils
  • MapUtils
  • IterableUtils
  • IteratorUtils
  • ComparatorUtils
  • Predicate
  • Transformer
  • Bag
  • MultiValuedMap

常见方法:

1
2
3
4
5
6
7
8
CollectionUtils.isEmpty(collection);
CollectionUtils.isNotEmpty(collection);
CollectionUtils.emptyIfNull(collection);
CollectionUtils.union(a, b);
CollectionUtils.intersection(a, b);
CollectionUtils.subtract(a, b);
CollectionUtils.disjunction(a, b);
CollectionUtils.isEqualCollection(a, b);

例如计算两个集合的交集、差集:

1
2
3
4
5
6
7
8
9
10
List<Long> oldIds = List.of(1L, 2L, 3L);
List<Long> newIds = List.of(2L, 3L, 4L);

Collection<Long> added = CollectionUtils.subtract(newIds, oldIds);
Collection<Long> removed = CollectionUtils.subtract(oldIds, newIds);
Collection<Long> exists = CollectionUtils.intersection(oldIds, newIds);

System.out.println("新增:" + added);
System.out.println("删除:" + removed);
System.out.println("保留:" + exists);

在权限、菜单、角色、商品标签、结算单关联关系等场景里,集合差集非常常用。

3. MapUtils

MapUtils 适合从 Map 中安全取值。

1
2
3
4
String name = MapUtils.getString(map, "name");
Integer age = MapUtils.getInteger(map, "age");
Long userId = MapUtils.getLong(map, "userId");
Boolean enabled = MapUtils.getBoolean(map, "enabled");

但要注意:Map 取值工具会弱化类型约束。业务核心代码里,如果长期依赖 Map<String, Object>,说明模型设计可能已经开始“泥石流”了。

建议:

  • 边界层可以用 Map;
  • 解析层可以用 Map;
  • 业务核心层尽量转成 DTO / Command / Entity。

4. Guava Immutable Collections

Guava 最值得使用的能力之一,就是不可变集合。

1
2
3
4
5
6
ImmutableList<String> names = ImmutableList.of("Tom", "Jerry");

ImmutableMap<String, Integer> scoreMap = ImmutableMap.of(
"Tom", 90,
"Jerry", 95
);

不可变集合的意义不是“少写代码”,而是表达:

这个集合创建之后不应该再被修改。

这对配置、常量、策略表、白名单、枚举映射非常有价值。

1
2
3
4
5
private static final ImmutableMap<String, BillType> BILL_TYPE_MAP =
ImmutableMap.<String, BillType>builder()
.put("AR", BillType.RECEIVABLE)
.put("AP", BillType.PAYABLE)
.build();

如果你用普通 HashMap

1
private static final Map<String, BillType> BILL_TYPE_MAP = new HashMap<>();

即使变量是 final,Map 内容仍然可以被修改。final 只保证引用不变,不保证对象内部状态不变。

5. Guava Multimap

Java 里经常遇到一对多关系:

1
Map<Long, List<Order>> userOrderMap = new HashMap<>();

传统写法:

1
userOrderMap.computeIfAbsent(userId, k -> new ArrayList<>()).add(order);

Guava 可以用 Multimap

1
2
3
4
5
6
7
Multimap<Long, Order> userOrderMap = ArrayListMultimap.create();

for (Order order : orders) {
userOrderMap.put(order.getUserId(), order);
}

Collection<Order> userOrders = userOrderMap.get(userId);

这个模型更自然:一个 key 对应多个 value,本来就是 Multimap。

适用场景:

  • 用户 -> 订单;
  • 店铺 -> 商品;
  • 角色 -> 权限;
  • 分类 -> 标签;
  • 批次号 -> 明细;
  • 单据 ID -> 操作日志。

6. Guava Multiset

Multiset 可以理解为“带计数的 Set”。

1
2
3
4
5
6
7
Multiset<String> words = HashMultiset.create();

words.add("apple");
words.add("apple");
words.add("banana");

System.out.println(words.count("apple")); // 2

适合:

  • 词频统计;
  • SKU 出现次数统计;
  • 错误码统计;
  • 任务状态数量统计;
  • 数据重复值分析。

7. Spring CollectionUtils

Spring 的 CollectionUtils 常用方法:

1
2
3
4
5
CollectionUtils.isEmpty(collection);
CollectionUtils.isEmpty(map);
CollectionUtils.containsAny(source, candidates);
CollectionUtils.findFirstMatch(source, candidates);
CollectionUtils.hasUniqueObject(collection);

Spring 里还有一个很好用的结构:MultiValueMap

1
2
3
4
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("tag", "java");
params.add("tag", "spring");
params.add("page", "1");

它在 HTTP 参数、Header、表单提交里很常见。

8. Hutool CollUtil

Hutool 的 CollUtil 是业务开发利器。

1
2
3
4
5
6
7
CollUtil.isEmpty(list);
CollUtil.isNotEmpty(list);
CollUtil.newArrayList("a", "b", "c");
CollUtil.join(list, ",");
CollUtil.getFirst(list);
CollUtil.subtract(list1, list2);
CollUtil.intersection(list1, list2);

如果项目已经大量使用 Hutool,业务层用 CollUtil 很舒服。但如果项目已经统一用 Apache Commons Collections,就没必要再混一套集合工具。


六、IO 与文件工具类:编码、关闭、大小,是三座大坑

1. JDK Files / Path

现代 Java 文件操作优先使用 PathFiles

1
2
3
4
5
6
7
Path path = Path.of("/tmp/demo.txt");

Files.writeString(path, "hello", StandardCharsets.UTF_8);

String content = Files.readString(path, StandardCharsets.UTF_8);

List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);

创建目录:

1
Files.createDirectories(Path.of("/tmp/app/logs"));

复制文件:

1
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);

遍历目录:

1
2
3
4
5
try (Stream<Path> stream = Files.walk(root)) {
List<Path> files = stream
.filter(Files::isRegularFile)
.toList();
}

注意:Files.walk 返回的是 Stream,需要关闭,所以要放在 try-with-resources 里。

2. Apache Commons IO:FileUtils / IOUtils

commons-io 很适合简化传统 IO。

常用类:

  • FileUtils
  • IOUtils
  • FilenameUtils
  • FileNameUtils
  • LineIterator
  • FileFilterUtils
  • IOCase
  • ByteOrderMark
  • BOMInputStream

读取文件:

1
String content = FileUtils.readFileToString(file, StandardCharsets.UTF_8);

写文件:

1
FileUtils.writeStringToFile(file, content, StandardCharsets.UTF_8);

复制目录:

1
FileUtils.copyDirectory(srcDir, destDir);

删除目录:

1
FileUtils.deleteDirectory(dir);

流复制:

1
2
3
4
try (InputStream in = ...;
OutputStream out = ...) {
IOUtils.copy(in, out);
}

读取流为字符串:

1
String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8);

这里要特别注意:字符集一定要显式指定。

错误示例:

1
String content = IOUtils.toString(inputStream);

这种写法依赖平台默认编码,在开发机没问题,上生产可能就变成乱码。线上乱码就像幽灵,你看不见它,但它会在凌晨三点准时拍你肩膀。

3. FilenameUtils

处理文件名不要手写字符串截取。

1
2
3
4
String name = FilenameUtils.getName(path);
String baseName = FilenameUtils.getBaseName(path);
String extension = FilenameUtils.getExtension(path);
String fullPath = FilenameUtils.getFullPath(path);

例如:

1
2
3
4
String fileName = "report.final.v2.xlsx";

System.out.println(FilenameUtils.getBaseName(fileName)); // report.final.v2
System.out.println(FilenameUtils.getExtension(fileName)); // xlsx

4. Hutool FileUtil / IoUtil

Hutool 的文件工具更偏快捷。

1
2
3
4
5
String content = FileUtil.readUtf8String(file);
FileUtil.writeUtf8String(content, file);
FileUtil.copy(src, dest, true);
FileUtil.del(file);
List<String> lines = FileUtil.readUtf8Lines(file);

流工具:

1
2
3
byte[] bytes = IoUtil.readBytes(inputStream);
String content = IoUtil.read(inputStream, StandardCharsets.UTF_8);
IoUtil.copy(inputStream, outputStream);

业务小工具、脚本、后台管理系统里非常方便。

5. Spring Resource / StreamUtils / FileCopyUtils

Spring 项目里读取 classpath 资源,建议使用 Resource 抽象。

1
2
3
4
5
Resource resource = new ClassPathResource("template/order.txt");

try (InputStream inputStream = resource.getInputStream()) {
String content = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
}

不要迷信 ResourceUtils.getFile("classpath:xxx")。在本地开发时它可能能拿到文件,但打成 jar 后,classpath 资源不一定是真实文件系统里的
File。更稳妥的是使用 Resource#getInputStream()


七、日期时间工具类:优先 java.time,少用 Date

1. JDK java.time

Java 8 后,应优先使用 java.time

常用类:

  • LocalDate
  • LocalTime
  • LocalDateTime
  • ZonedDateTime
  • Instant
  • Duration
  • Period
  • DateTimeFormatter
  • ZoneId

示例:

1
2
3
4
5
6
7
LocalDate today = LocalDate.now();

LocalDate nextMonth = today.plusMonths(1);

LocalDateTime now = LocalDateTime.now();

String text = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

时间间隔:

1
2
3
4
5
6
7
8
9
Instant start = Instant.now();

// do something

Instant end = Instant.now();

Duration duration = Duration.between(start, end);

System.out.println(duration.toMillis());

2. DateTimeFormatter

不要反复 new SimpleDateFormatSimpleDateFormat 不是线程安全的。

推荐:

1
2
3
4
private static final DateTimeFormatter DATE_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

String text = LocalDateTime.now().format(DATE_TIME_FORMATTER);

3. Hutool DateUtil

如果你的项目里还有大量 Date,Hutool 的 DateUtil 很好用。

1
2
3
4
5
6
7
8
9
Date now = DateUtil.date();

String nowText = DateUtil.now();

String today = DateUtil.today();

Date date = DateUtil.parse("2026-06-07 12:00:00");

String text = DateUtil.formatDateTime(date);

它适合老项目、遗留代码、后台管理系统。但新代码建议优先 java.time

4. 时间工具的团队建议

建议统一规范:

  • 新业务实体字段:优先 LocalDateTime / LocalDate / Instant
  • 数据库时间:明确时区策略;
  • 前后端传输:统一 ISO-8601 或固定格式;
  • 老接口兼容:可用 DateUtil 做转换;
  • 不要在代码里到处散落 "yyyy-MM-dd HH:mm:ss"

八、Bean、反射与对象拷贝:最方便,也最危险

1. Spring BeanUtils

Spring 的 BeanUtils.copyProperties 很常见。

1
2
UserDTO dto = new UserDTO();
BeanUtils.copyProperties(user, dto);

它适合简单对象之间的浅拷贝。

注意:它是浅拷贝,不是深拷贝。

1
source.getAddress() == target.getAddress()

复杂嵌套对象、集合对象、类型转换、字段名不同的情况,不建议用它硬拷。

错误示例:

1
BeanUtils.copyProperties(orderEntity, orderDetailVO);

如果里面有:

  • BigDecimalString
  • Integer 转枚举
  • List<Entity>List<VO>
  • 字段名不一致
  • 嵌套对象转换

那就别用 BeanUtils 硬扛。应该用 MapStruct 或手写转换器。

2. Hutool BeanUtil

Hutool BeanUtil 更灵活。

1
UserDTO dto = BeanUtil.copyProperties(user, UserDTO.class);

忽略 null:

1
2
3
BeanUtil.copyProperties(source, target, CopyOptions.create()
.setIgnoreNullValue(true)
.setIgnoreError(true));

Map 转 Bean:

1
User user = BeanUtil.toBean(map, User.class);

Bean 转 Map:

1
Map<String, Object> map = BeanUtil.beanToMap(user);

Hutool BeanUtil 在业务开发中很方便,尤其适合:

  • DTO 转换;
  • Map 和 Bean 互转;
  • 部分字段更新;
  • 后台管理系统;
  • 导入导出场景。

但也不要滥用。对象转换如果承载业务语义,建议明确写转换逻辑。

3. Jodd BeanUtil

Jodd 的 BeanUtil 也非常强,支持属性读写、嵌套属性、List、Map、数组路径访问等。

示例风格:

1
2
3
BeanUtil.pojo.setProperty(user, "name", "Mario");

Object value = BeanUtil.pojo.getProperty(user, "name");

嵌套属性:

1
BeanUtil.pojo.getProperty(order, "items[0].skuCode");

这类工具适合通用框架、配置解析、动态属性访问。业务核心逻辑里要慎用,因为字符串路径没有编译期检查。

4. Spring ReflectionUtils

Spring 的 ReflectionUtils 适合框架层反射。

1
2
3
Field field = ReflectionUtils.findField(User.class, "name");
ReflectionUtils.makeAccessible(field);
Object value = ReflectionUtils.getField(field, user);

反射工具类的使用原则:

  • 框架层可以用;
  • 基础设施可以用;
  • 业务核心少用;
  • 能用正常方法调用,就不要反射;
  • 反射结果要缓存,否则性能和可读性都不好。

5. Apache Commons BeanUtils

Apache Commons BeanUtils 历史很久,能力偏 JavaBean 反射、属性填充、类型转换。

1
2
3
BeanUtils.copyProperties(dest, orig);
BeanUtils.setProperty(bean, "name", "Mario");
String name = BeanUtils.getProperty(bean, "name");

但是它的类型转换、性能、异常处理、历史安全问题都需要谨慎看待。现代项目里,简单拷贝可以用 Spring/Hutool,复杂映射推荐
MapStruct。

6. 对象拷贝建议

建议按复杂度分层:

场景 推荐方式
简单 POJO 同名字段浅拷贝 Spring BeanUtils / Hutool BeanUtil
DTO 与 Entity 字段稍有差异 手写 converter
大量对象映射、编译期安全 MapStruct
嵌套对象、集合转换 MapStruct / 手写
动态属性路径 Jodd BeanUtil / Spring BeanWrapper
Map 与 Bean 互转 Hutool BeanUtil
高性能核心链路 手写,少反射

九、类型转换工具类:Convert、ConversionService 与 TypeHandler

1. JDK 原生转换

基础转换可以用 JDK:

1
2
3
4
5
Integer.parseInt("123");
Long.valueOf("123");
Boolean.parseBoolean("true");
BigDecimal value = new BigDecimal("12.34");
Enum.valueOf(OrderStatus.class, "PAID");

简单、清晰、无额外依赖。

2. Hutool Convert

Hutool 的 Convert 很适合处理边界层数据。

1
2
3
4
5
Integer age = Convert.toInt(value, 0);
Long userId = Convert.toLong(value);
Boolean enabled = Convert.toBool(value, false);
BigDecimal amount = Convert.toBigDecimal(value);
String str = Convert.toStr(value);

例如请求参数、Excel 导入、Map 数据解析:

1
2
Integer quantity = Convert.toInt(row.get("quantity"), 0);
BigDecimal price = Convert.toBigDecimal(row.get("price"), BigDecimal.ZERO);

注意:Convert 很方便,但不要在核心领域模型里大量吞默认值。比如金额转换失败直接变成 BigDecimal.ZERO,可能会把数据错误伪装成正常业务。

边界层可以容错,核心层必须严谨。

3. Spring ConversionService

Spring 的类型转换体系更适合框架级转换。

1
2
3
ConversionService conversionService = ...;

Integer value = conversionService.convert("123", Integer.class);

自定义 Converter:

1
2
3
4
5
6
7
8
@Component
public class StringToOrderStatusConverter implements Converter<String, OrderStatus> {

@Override
public OrderStatus convert(String source) {
return OrderStatus.of(source);
}
}

适合:

  • Spring MVC 参数绑定;
  • 配置属性绑定;
  • 表单转换;
  • 框架级类型转换;
  • 枚举转换。

4. MyBatis TypeHandler

MyBatis 的类型转换核心是 TypeHandler

比如枚举与数据库字段转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class OrderStatusTypeHandler extends BaseTypeHandler<OrderStatus> {

@Override
public void setNonNullParameter(
PreparedStatement ps,
int i,
OrderStatus parameter,
JdbcType jdbcType
) throws SQLException {
ps.setString(i, parameter.getCode());
}

@Override
public OrderStatus getNullableResult(ResultSet rs, String columnName) throws SQLException {
return OrderStatus.of(rs.getString(columnName));
}

@Override
public OrderStatus getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return OrderStatus.of(rs.getString(columnIndex));
}

@Override
public OrderStatus getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return OrderStatus.of(cs.getString(columnIndex));
}
}

MyBatis 场景里,不要在 SQL 查询后再到处手动转换枚举。统一交给 TypeHandler,代码会干净很多。


十、Apache Tika:文件识别与内容抽取

Tika 的定位不是普通工具类,而是内容分析工具箱。

1. 文件类型识别

1
2
3
4
5
Tika tika = new Tika();

String mimeType = tika.detect(file);

System.out.println(mimeType);

可以用于:

  • 上传文件校验;
  • 判断真实文件类型;
  • 搜索索引前处理;
  • 内容安全扫描前置判断。

不要只相信文件后缀:

1
report.pdf

它可能根本不是 PDF。后缀只是名字,MIME 检测才更接近内容本身。

2. 文本抽取

1
2
3
Tika tika = new Tika();

String text = tika.parseToString(file);

适合从 PDF、Word、Excel、PPT 等文档中抽取文本。

比如做一个简单的文档搜索:

1
2
3
4
5
6
7
8
public DocumentIndex buildIndex(File file) throws IOException, TikaException {
Tika tika = new Tika();

String mimeType = tika.detect(file);
String content = tika.parseToString(file);

return new DocumentIndex(file.getName(), mimeType, content);
}

3. Tika 使用注意事项

Tika 很强,但不要随便在主业务线程里解析大文件。

建议:

  1. 文件大小限制;
  2. 解析超时控制;
  3. 异步任务处理;
  4. 对不可信文件做隔离;
  5. 控制 parser 依赖;
  6. 解析失败要降级;
  7. 不要把全文直接塞进日志;
  8. 大文件不要一次性读入内存。

如果你做的是文档系统、知识库、搜索系统,Tika 很有价值。如果只是上传头像,就别上 Tika 全家桶了,杀鸡别用高达。


十一、Guava:不只是工具类,而是建模能力

Guava 很多类不是“工具方法”,而是让代码表达更准确。

1. Preconditions

前面已经讲过,适合参数和状态校验。

1
2
checkArgument(pageNo > 0, "pageNo must be positive");
checkArgument(pageSize > 0 && pageSize <= 500, "invalid pageSize");

2. ImmutableMap 做策略表

1
2
3
4
5
private static final ImmutableMap<String, Function<Order, BigDecimal>> AMOUNT_CALCULATORS =
ImmutableMap.<String, Function<Order, BigDecimal>>builder()
.put("AR", Order::calculateReceivableAmount)
.put("AP", Order::calculatePayableAmount)
.build();

这比一堆 if-else 更稳定:

1
2
3
4
5
6
7
Function<Order, BigDecimal> calculator = AMOUNT_CALCULATORS.get(order.getBillType());

if (calculator == null) {
throw new IllegalArgumentException("unsupported bill type");
}

BigDecimal amount = calculator.apply(order);

3. CacheBuilder 本地缓存

1
2
3
4
5
6
7
8
9
LoadingCache<Long, User> userCache = CacheBuilder.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(new CacheLoader<>() {
@Override
public User load(Long userId) {
return userRepository.findById(userId);
}
});

使用:

1
User user = userCache.get(userId);

适合:

  • 本地热点数据;
  • 字典配置;
  • 低频变化数据;
  • 单机临时缓存;
  • 降低重复计算。

不适合:

  • 分布式一致性缓存;
  • 强一致数据;
  • 多实例共享状态;
  • 订单金额、库存扣减这类核心状态。

分布式缓存用 Redis,本地缓存用 Guava/Caffeine。边界要清楚,不然缓存会从优化工具变成线上事故制造机。

4. RateLimiter

Guava RateLimiter 可做单机限流。

1
2
3
4
5
RateLimiter limiter = RateLimiter.create(100.0);

if (limiter.tryAcquire()) {
doSomething();
}

注意:这是单机限流,不是分布式限流。多实例部署时,每个实例都有自己的限流器。

5. Guava 使用建议

推荐使用:

  • Preconditions
  • ImmutableXXX
  • Multimap
  • Multiset
  • BiMap
  • CacheBuilder
  • RateLimiter
  • Splitter
  • Joiner

谨慎使用:

  • Guava Optional,现代 Java 项目更建议用 java.util.Optional
  • 在公共 API 返回 Guava 类型,除非团队明确接受;
  • Android 项目要注意 Guava 版本和体积;
  • 和 Caffeine、Apache Commons 功能重复时要统一规范。

十二、Hutool:业务开发的瑞士军刀

Hutool 的优势是“什么都有,而且好上手”。

1. StrUtil

1
2
3
StrUtil.isBlank(name);
StrUtil.blankToDefault(name, "unknown");
StrUtil.format("订单 {} 支付成功", orderNo);

2. CollUtil

1
2
3
4
5
6
7
List<String> list = CollUtil.newArrayList("a", "b", "c");

String text = CollUtil.join(list, ",");

List<String> intersection = CollUtil.intersection(list1, list2)
.stream()
.toList();

3. Convert

1
2
Integer age = Convert.toInt(value, 0);
LocalDateTime time = Convert.toLocalDateTime(value);

4. DateUtil

1
2
Date begin = DateUtil.beginOfDay(new Date());
Date end = DateUtil.endOfDay(new Date());

5. BeanUtil

1
UserDTO dto = BeanUtil.copyProperties(user, UserDTO.class);

6. FileUtil

1
2
String content = FileUtil.readUtf8String(file);
FileUtil.writeUtf8String(content, file);

7. IdUtil

1
2
3
String uuid = IdUtil.fastSimpleUUID();

long snowflakeId = IdUtil.getSnowflake(1, 1).nextId();

注意:雪花 ID 的 workerId、datacenterId 不能随便写死到所有机器一样,否则分布式环境可能冲突。

8. HttpUtil

1
String result = HttpUtil.get("https://api.example.com/users");

适合简单 HTTP 调用。但复杂生产场景,比如连接池、重试、熔断、超时治理、链路追踪,建议用 Spring RestClientWebClient、OkHttp 或
Apache HttpClient。

9. Hutool 使用建议

适合:

  • 业务系统;
  • 后台管理;
  • 数据导入导出;
  • 小工具;
  • 快速开发;
  • 中文团队。

谨慎:

  • 基础框架层;
  • starter;
  • SDK;
  • 强依赖边界项目;
  • 对依赖体积敏感的服务;
  • 安全要求很高的场景。

建议:

1
2
3
4
5
<!-- 优先按模块引入 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>

而不是所有地方都:

1
2
3
4
5

<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>

hutool-all 很爽,但爽也要有边界。代码世界里,最贵的两个字就是“随便”。


十三、Jodd:轻量工具与微框架思路

Jodd 的存在感不如 Commons、Guava、Hutool,但它有自己的特点:轻量、独立、少依赖。

1. Jodd StringUtil

StringUtil 提供很多字符串增强能力:

1
2
3
4
StringUtil.isBlank(str);
StringUtil.isEmpty(str);
StringUtil.replace(str, "a", "b");
StringUtil.split(str, ",");

2. Jodd BeanUtil

Jodd BeanUtil 适合动态属性访问:

1
2
3
BeanUtil.pojo.setProperty(user, "name", "Mario");

Object name = BeanUtil.pojo.getProperty(user, "name");

嵌套:

1
Object value = BeanUtil.pojo.getProperty(order, "items[0].skuCode");

这类能力适合框架、规则引擎、配置映射,不建议在普通业务代码里频繁使用字符串路径。

3. Jodd Props

Jodd Props 是对 Java properties 的增强,支持 UTF-8、sections、profiles、macros 等。

如果你不想引完整配置中心,但又觉得 JDK Properties 太弱,Jodd Props 是个轻量选择。

4. Jodd HTTP / JSON

Jodd HTTP 是轻量 HTTP 客户端,Jodd JSON 是轻量 JSON 序列化解析工具。

不过在现代 Spring Boot 项目里:

  • JSON 通常由 Jackson 负责;
  • HTTP 通常由 RestClient/WebClient/OkHttp/Apache HttpClient 负责;
  • 配置通常由 Spring Boot 配置体系负责。

所以 Jodd 更适合轻量独立项目,而不是大型 Spring 体系里重复造一层。


十四、Spring 常用工具类深度盘点

1. Assert

1
2
3
Assert.notNull(obj, "obj must not be null");
Assert.hasText(text, "text must have text");
Assert.notEmpty(collection, "collection must not be empty");

适合框架内部参数校验。

2. CollectionUtils

1
2
CollectionUtils.isEmpty(list);
CollectionUtils.isEmpty(map);

3. ObjectUtils

1
2
ObjectUtils.isEmpty(obj);
ObjectUtils.nullSafeEquals(a, b);

4. ClassUtils

1
2
3
ClassUtils.getShortName(UserService.class);
ClassUtils.isPresent("com.example.Demo", classLoader);
ClassUtils.getDefaultClassLoader();

适合写 starter、自动配置、条件装配时使用。

5. ReflectionUtils

1
2
3
Method method = ReflectionUtils.findMethod(User.class, "getName");
ReflectionUtils.makeAccessible(method);
Object value = ReflectionUtils.invokeMethod(method, user);

6. BeanUtils

1
BeanUtils.copyProperties(source, target);

只适合简单浅拷贝。

7. LinkedMultiValueMap

1
2
3
4
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.add("username", "mario");
form.add("role", "admin");
form.add("role", "developer");

适合请求参数、表单、Header 等一 key 多 value 场景。

8. StopWatch

1
2
3
4
5
6
7
8
9
10
11
StopWatch stopWatch = new StopWatch("order-create");

stopWatch.start("validate");
validate(command);
stopWatch.stop();

stopWatch.start("save");
saveOrder(command);
stopWatch.stop();

System.out.println(stopWatch.prettyPrint());

适合本地调试、性能分段分析。生产链路监控还是建议用 Micrometer、日志埋点、Tracing。

9. Resource

1
2
3
4
5
Resource resource = new ClassPathResource("template/order.txt");

try (InputStream inputStream = resource.getInputStream()) {
String content = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
}

Spring 的资源抽象非常重要。它能统一处理:

  • classpath;
  • file;
  • URL;
  • servlet context;
  • jar 内资源。

10. UriComponentsBuilder

1
2
3
4
5
String url = UriComponentsBuilder
.fromHttpUrl("https://api.example.com/orders")
.queryParam("userId", userId)
.queryParam("status", "PAID")
.toUriString();

不要手动拼 URL:

1
String url = baseUrl + "?userId=" + userId + "&status=" + status;

一旦遇到 URL 编码、空格、特殊字符、重复参数,手写拼接很容易翻车。


十五、MyBatis 中常见工具和内部组件

MyBatis 不是工具类库,但它内部有一些常用组件值得了解。

1. Resources

MyBatis Resources 可用于读取 classpath 资源:

1
2
3
try (Reader reader = Resources.getResourceAsReader("mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
}

在纯 MyBatis 项目里很常见。

如果是 Spring Boot + MyBatis,通常不需要自己用它读取配置,交给自动配置即可。

2. TypeAliasRegistry

MyBatis 支持类型别名:

1
2
3
4

<typeAliases>
<typeAlias alias="User" type="com.example.User"/>
</typeAliases>

底层由 TypeAliasRegistry 管理。

在自定义 MyBatis 扩展时可能会接触:

1
configuration.getTypeAliasRegistry().registerAlias("User", User.class);

3. TypeHandlerRegistry

类型处理器注册表。

1
2
configuration.getTypeHandlerRegistry()
.register(OrderStatus.class, OrderStatusTypeHandler.class);

建议把数据库字段和 Java 类型的转换逻辑沉到 TypeHandler,而不是散落在业务代码里。

4. MetaObject

MetaObject 是 MyBatis 反射系统的重要对象,用于读取和设置对象属性。

1
2
3
4
5
MetaObject metaObject = configuration.newMetaObject(user);

Object name = metaObject.getValue("name");

metaObject.setValue("name", "Mario");

它也支持嵌套属性路径。插件、自动填充、审计字段处理时可能会用到。

5. GenericTokenParser

MyBatis 内部解析 ${}#{} 这类 token 时会使用类似 token parser 的机制。你写自定义 SQL 解析器、模板替换器时可以参考这种设计:

核心思想是:

  • 定义开始 token;
  • 定义结束 token;
  • 遇到 token 后交给 handler 处理;
  • 输出处理后的字符串。

这比粗暴正则替换更可控。

6. MyBatis 工具使用建议

  • 业务代码不要随意依赖 MyBatis 内部工具;
  • 插件、拦截器、自动填充、框架扩展可以使用;
  • Spring Boot 项目优先使用自动配置;
  • 类型转换优先 TypeHandler;
  • SQL 动态逻辑优先 XML / Provider / Dynamic SQL,而不是字符串拼接。

十六、JDK 常用工具类完整盘点

1. Objects

1
2
3
4
Objects.equals(a, b);
Objects.requireNonNull(obj);
Objects.hash(a, b, c);
Objects.toString(obj, "");

2. Optional

Optional 适合做返回值,不适合做字段。

推荐:

1
2
3
public Optional<User> findUser(Long userId) {
return userRepository.findById(userId);
}

使用:

1
2
User user = findUser(userId)
.orElseThrow(() -> new BizException("user not found"));

不推荐:

1
private Optional<String> name;

也不推荐方法参数:

1
2
public void update(Optional<String> name) {
}

参数可选应该用重载、DTO 字段或明确传 null 后校验,不要把 Optional 当新型 null 容器。

3. Collections

1
2
3
4
5
Collections.emptyList();
Collections.singletonList("a");
Collections.unmodifiableList(list);
Collections.sort(list);
Collections.reverse(list);

4. Arrays

1
2
3
4
5
Arrays.asList("a", "b");
Arrays.stream(array);
Arrays.copyOf(array, newLength);
Arrays.equals(a, b);
Arrays.deepEquals(a, b);

注意:

1
2
List<String> list = Arrays.asList("a", "b");
list.add("c"); // 抛异常

Arrays.asList 返回的是固定长度 List,不支持增删。

如果要可变:

1
List<String> list = new ArrayList<>(Arrays.asList("a", "b"));

5. Comparator

1
2
3
4
5
6
List<User> users = ...

users.sort(
Comparator.comparing(User::getAge)
.thenComparing(User::getName)
);

处理 null:

1
2
3
4
5
6
users.sort(
Comparator.comparing(
User::getAge,
Comparator.nullsLast(Integer::compareTo)
)
);

6. Base64

1
2
3
4
5
String encoded = Base64.getEncoder()
.encodeToString(bytes);

byte[] decoded = Base64.getDecoder()
.decode(encoded);

URL 安全:

1
2
3
String token = Base64.getUrlEncoder()
.withoutPadding()
.encodeToString(bytes);

7. UUID

1
String id = UUID.randomUUID().toString();

如果不想要横杠:

1
String id = UUID.randomUUID().toString().replace("-", "");

不过高并发分布式 ID,建议用专门的 ID 生成方案,例如雪花算法、数据库号段、Redis、自研发号器等。

8. Pattern

正则建议预编译:

1
2
3
4
5
6
private static final Pattern PHONE_PATTERN =
Pattern.compile("^1[3-9]\\d{9}$");

public boolean isPhone(String phone) {
return PHONE_PATTERN.matcher(phone).matches();
}

不要在高频方法里反复:

1
Pattern.compile(regex).matcher(value).matches();

9. HttpClient

Java 11 提供了标准 HTTP Client。

1
2
3
4
5
6
7
8
9
10
11
HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.GET()
.build();

HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)
);

适合:

  • 简单 HTTP 调用;
  • 无需复杂连接池封装;
  • 不想引第三方 HTTP 客户端。

复杂微服务调用仍建议使用成熟 HTTP 客户端并统一治理超时、重试、熔断、指标和链路追踪。

10. CompletableFuture

1
2
3
4
5
6
7
8
9
10
11
12
CompletableFuture<User> userFuture =
CompletableFuture.supplyAsync(() -> userService.getUser(userId));

CompletableFuture<List<Order>> orderFuture =
CompletableFuture.supplyAsync(() -> orderService.listOrders(userId));

CompletableFuture<Void> all = CompletableFuture.allOf(userFuture, orderFuture);

all.join();

User user = userFuture.join();
List<Order> orders = orderFuture.join();

注意:默认线程池是 ForkJoinPool.commonPool(),业务系统中建议显式传入自定义线程池。

1
CompletableFuture.supplyAsync(task, executor);

十七、常见工具类冲突与团队规范

1. StringUtils 冲突

常见冲突:

1
2
org.apache.commons.lang3.StringUtils
org.springframework.util.StringUtils

建议:

  • 业务层统一 Apache StringUtils 或 Hutool StrUtil
  • Spring 框架层使用 Spring StringUtils
  • 不要一个类里混用两个 StringUtils

2. ObjectUtils 冲突

常见冲突:

1
2
org.apache.commons.lang3.ObjectUtils
org.springframework.util.ObjectUtils

建议:

  • 普通业务默认 JDK Objects
  • 多默认值、对象空判断用 Apache;
  • Spring 框架内部用 Spring。

3. BeanUtils 冲突

常见冲突:

1
2
org.springframework.beans.BeanUtils
org.apache.commons.beanutils.BeanUtils

两个方法签名和行为不同,很容易看错。

Spring:

1
BeanUtils.copyProperties(source, target);

Apache Commons:

1
BeanUtils.copyProperties(dest, orig);

参数顺序就足够让人踩坑。

建议:

  • 项目里尽量只用一种;
  • 不要静态导入;
  • 复杂转换不要用 BeanUtils;
  • 建议封装成明确的 converter。

4. Optional 冲突

现在项目里不要再使用 Guava Optional 作为新代码首选。

推荐:

1
java.util.Optional

除非老项目历史原因已经深度使用 Guava Optional。

5. 日期工具冲突

不要同时大量混用:

  • Date
  • Calendar
  • LocalDateTime
  • Instant
  • Hutool DateTime
  • Joda-Time

建议:

  • 新代码统一 java.time
  • 老代码边界转换;
  • 数据库存储策略统一;
  • DTO 时间格式统一。

十八、推荐的团队工具类优先级

可以制定如下规范。

1. 字符串

优先级:

  1. JDK String
  2. Apache StringUtils
  3. Hutool StrUtil
  4. Spring StringUtils 仅限 Spring 框架上下文

推荐:

1
2
StringUtils.isBlank(name);
StringUtils.equals(a, b);

2. 集合

优先级:

  1. JDK Collection / List / Map / Stream
  2. JDK Collections
  3. Apache Commons Collections
  4. Guava 特殊集合
  5. Hutool CollUtil

推荐:

1
CollectionUtils.isEmpty(list);

但要统一到底是 Spring 还是 Apache。

3. 文件 IO

优先级:

  1. JDK Files / Path
  2. Apache Commons IO
  3. Spring Resource
  4. Hutool FileUtil

推荐:

1
Files.readString(path, StandardCharsets.UTF_8);

4. Bean 拷贝

优先级:

  1. 手写 converter
  2. MapStruct
  3. Spring BeanUtils / Hutool BeanUtil
  4. 反射动态工具

推荐:

1
UserDTO dto = UserConverter.INSTANCE.toDTO(user);

而不是所有地方都:

1
BeanUtils.copyProperties(user, dto);

5. 参数校验

优先级:

  1. 业务异常手写判断
  2. JDK Objects.requireNonNull
  3. Guava Preconditions
  4. Spring Assert

业务校验:

1
2
3
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new BizException(ErrorCode.INVALID_AMOUNT);
}

框架校验:

1
Assert.notNull(beanFactory, "beanFactory must not be null");

6. 日期时间

优先级:

  1. java.time
  2. Hutool DateUtil 兼容旧 Date
  3. 避免继续使用 Calendar
  4. 不建议新项目引 Joda-Time

7. HTTP

优先级:

  1. Spring 项目:RestClient / WebClient
  2. 简单 JDK 项目:JDK HttpClient
  3. 轻量脚本:Hutool HttpUtil / Jodd Http
  4. 复杂连接治理:OkHttp / Apache HttpClient

8. 文档解析

优先级:

  1. 简单 MIME:Tika Core
  2. 文档全文抽取:Tika Parsers
  3. 大文件解析:异步 + 超时 + 隔离
  4. 不可信文件:安全沙箱或独立服务

十九、实际业务案例:订单导入场景的工具类组合

假设有一个 Excel/CSV 订单导入场景,流程如下:

  1. 上传文件;
  2. 判断文件类型;
  3. 保存临时文件;
  4. 读取内容;
  5. 转成 DTO;
  6. 校验字段;
  7. 分组处理;
  8. 保存数据库;
  9. 返回错误报告。

可以这样组合:

1. 文件名与类型

1
2
3
4
5
String extension = FilenameUtils.getExtension(fileName);

if (!Set.of("xlsx", "csv").contains(extension)) {
throw new BizException("unsupported file type");
}

更进一步用 Tika:

1
String mimeType = new Tika().detect(file);

2. 保存文件

1
2
3
4
5
Path uploadPath = Path.of("/data/upload", fileName);

Files.createDirectories(uploadPath.getParent());

Files.copy(inputStream, uploadPath, StandardCopyOption.REPLACE_EXISTING);

3. 字段判断

1
2
3
if (StringUtils.isBlank(row.getOrderNo())) {
errors.add("订单号不能为空");
}

4. 类型转换

1
2
3
4
5
BigDecimal amount = Convert.toBigDecimal(row.getAmount(), null);

if (amount == null) {
errors.add("金额格式错误");
}

5. 分组

1
2
Map<String, List<OrderImportRow>> groupByShop =
rows.stream().collect(Collectors.groupingBy(OrderImportRow::getShopCode));

或者 Guava:

1
2
3
4
5
Multimap<String, OrderImportRow> groupByShop = ArrayListMultimap.create();

for (OrderImportRow row : rows) {
groupByShop.put(row.getShopCode(), row);
}

6. 差异计算

1
2
Collection<String> newOrderNos = CollectionUtils.subtract(importOrderNos, existsOrderNos);
Collection<String> duplicatedOrderNos = CollectionUtils.intersection(importOrderNos, existsOrderNos);

7. DTO 转 Entity

简单:

1
OrderEntity entity = BeanUtil.copyProperties(row, OrderEntity.class);

复杂:

1
OrderEntity entity = OrderImportConverter.toEntity(row);

这里更推荐手写或 MapStruct,因为导入通常有大量业务语义。


二十、实际业务案例:财务结算场景的工具类组合

财务结算类系统里,工具类更要克制。因为金额、状态、账期、结余,一旦因为默认值处理不当出错,后果很麻烦。

1. 金额不要随便 default zero

危险写法:

1
BigDecimal amount = Convert.toBigDecimal(value, BigDecimal.ZERO);

如果 value 是非法字符串,直接变成 0,可能掩盖数据问题。

更安全:

1
2
3
4
5
6
7
BigDecimal amount;

try {
amount = new BigDecimal(value);
} catch (NumberFormatException ex) {
throw new BizException("金额格式错误");
}

或者边界层记录错误,不进入核心计算。

2. 账期判断

1
2
boolean overlap = existingStart.compareTo(currentEnd) <= 0
&& existingEnd.compareTo(currentStart) >= 0;

这个判断比花里胡哨的日期工具更重要。业务规则清楚,代码自然清楚。

3. 状态校验

1
checkState(bill.canSettle(), "bill can not settle");

或者业务异常:

1
2
3
if (!bill.canSettle()) {
throw new BizException(ErrorCode.BILL_STATUS_NOT_ALLOWED);
}

4. 日志字符串

1
2
3
4
5
6
String msg = StrUtil.format(
"结算单处理完成, billNo={}, billType={}, amount={}",
billNo,
billType,
amount
);

但如果是正式日志,更建议用日志框架占位符:

1
2
log.info("结算单处理完成, billNo={}, billType={}, amount={}",
billNo, billType, amount);

不要为了字符串工具类牺牲日志框架的延迟格式化能力。


二十一、哪些工具类不建议滥用

1. BeanUtils 不要替代领域转换

错误倾向:

1
BeanUtils.copyProperties(a, b);

满项目都是这句,看似优雅,实际没人知道字段是怎么过去的。

建议关键对象转换明确写:

1
2
3
4
5
6
7
public static OrderDTO toDTO(Order order) {
OrderDTO dto = new OrderDTO();
dto.setOrderNo(order.getOrderNo());
dto.setAmount(order.getAmount());
dto.setStatusName(order.getStatus().getName());
return dto;
}

2. ObjectUtils.isEmpty 不要承载复杂业务语义

1
2
if (ObjectUtils.isEmpty(value)) {
}

这句话太宽泛。value 是字符串?集合?数组?Optional?不同语义混在一起,后续维护者容易误判。

3. Convert.toXXX 默认值要慎重

1
Convert.toInt(value, 0)

适合 UI 展示、导入容错,不适合核心计算。

4. FileUtils.readFileToString 不要读超大文件

1
String content = FileUtils.readFileToString(file, StandardCharsets.UTF_8);

小文件没问题,大文件会直接吃内存。大文件应使用流式处理。

5. StringSubstitutor 不要处理不可信复杂模板

简单 Map 替换没问题,高级 lookup 要谨慎。

6. Hutool-all 不要在基础库里随便引

业务系统可以,基础 SDK、starter、公共组件要克制。否则别人引你的一个小工具,顺手带走一车依赖,像买螺丝送挖掘机。


二十二、项目里是否还需要自定义 XXXUtil?

需要,但要少。

自定义工具类应该满足两个条件:

  1. 承载业务语义;
  2. 第三方工具无法准确表达。

例如可以有:

1
2
3
4
5
6
MoneyUtils
BillPeriodUtils
OrderStatusUtils
TenantContextUtils
TraceLogUtils
SettlementAmountUtils

不建议有:

1
2
3
4
5
StringUtil
DateUtil
CollectionUtil
BeanUtil
ObjectUtil

这些基础工具类成熟库已经做得很好了。自己再写一套,通常只会制造 Bug。

好的业务工具类示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final class BillPeriodUtils {

private BillPeriodUtils() {
}

public static boolean isOverlap(
LocalDate existingStart,
LocalDate existingEnd,
LocalDate currentStart,
LocalDate currentEnd
) {
Objects.requireNonNull(existingStart, "existingStart must not be null");
Objects.requireNonNull(existingEnd, "existingEnd must not be null");
Objects.requireNonNull(currentStart, "currentStart must not be null");
Objects.requireNonNull(currentEnd, "currentEnd must not be null");

return existingStart.compareTo(currentEnd) <= 0
&& existingEnd.compareTo(currentStart) >= 0;
}
}

这不是重复造轮子,这是把业务规则命名。


二十三、推荐工具类清单

JDK 必会

  • Objects
  • Optional
  • Collections
  • Arrays
  • Comparator
  • Files
  • Path
  • Base64
  • UUID
  • Pattern
  • java.time
  • CompletableFuture
  • HttpClient

Apache Commons 推荐

  • StringUtils
  • ObjectUtils
  • ArrayUtils
  • RandomStringUtils
  • NumberUtils
  • BooleanUtils
  • CollectionUtils
  • ListUtils
  • SetUtils
  • MapUtils
  • FileUtils
  • IOUtils
  • FilenameUtils
  • StringSubstitutor
  • StringEscapeUtils

Guava 推荐

  • Preconditions
  • ImmutableList
  • ImmutableMap
  • ImmutableSet
  • Multimap
  • Multiset
  • BiMap
  • Table
  • Joiner
  • Splitter
  • CacheBuilder
  • RateLimiter

Hutool 推荐

  • StrUtil
  • ObjectUtil
  • CollUtil
  • MapUtil
  • BeanUtil
  • Convert
  • DateUtil
  • FileUtil
  • IoUtil
  • IdUtil
  • ReflectUtil
  • ClassUtil
  • HttpUtil
  • JSONUtil
  • SecureUtil

Spring 推荐

  • Assert
  • StringUtils
  • CollectionUtils
  • ObjectUtils
  • ClassUtils
  • ReflectionUtils
  • BeanUtils
  • Resource
  • StreamUtils
  • FileCopyUtils
  • LinkedMultiValueMap
  • UriComponentsBuilder
  • StopWatch

MyBatis 推荐了解

  • Resources
  • TypeAliasRegistry
  • TypeHandlerRegistry
  • MetaObject
  • SystemMetaObject
  • GenericTokenParser
  • PropertyTokenizer

二十四、最终选型建议

如果是普通 Spring Boot 后端项目,我建议这样选:

基础优先级

1
JDK > Spring 内置 > Apache Commons > Guava > Hutool > Jodd/Tika 按场景

字符串

1
Apache StringUtils 或 Hutool StrUtil,二选一统一

集合

1
2
JDK Stream + Apache CollectionUtils
特殊集合用 Guava

IO

1
2
3
JDK Files 优先
复杂文件操作用 Commons IO
Spring 资源读取用 Resource

Bean 拷贝

1
2
简单用 Spring/Hutool
复杂用 MapStruct/手写

日期

1
2
java.time 优先
老 Date 兼容用 Hutool DateUtil

HTTP

1
2
3
Spring 项目用 RestClient/WebClient
轻量脚本用 Hutool/Jodd
标准库可用 JDK HttpClient

文档解析

1
Tika 按需引入,注意隔离、超时、依赖体积

结语:工具类是秩序,不是捷径

优秀的 Java 项目,工具类不会到处乱飞,而是有清晰边界:

  • 哪些用 JDK;
  • 哪些用 Spring;
  • 哪些用 Apache Commons;
  • 哪些用 Guava;
  • 哪些用 Hutool;
  • 哪些必须业务自己封装;
  • 哪些坚决不能滥用。

工具类写得好,代码会更短;工具类选得好,系统会更稳;工具类边界管得好,团队协作会更轻松。

真正成熟的工程实践,不是看到一个 XXXUtil 就兴奋,而是知道它应该出现在哪里、不应该出现在哪里。

代码可以偷懒,架构不能糊涂。Hutool 可以“甜甜的”,但生产事故可一点都不甜。

参考资料建议

  • Oracle Java SE API 文档
  • Apache Commons Lang 官方文档
  • Apache Commons Collections 官方文档
  • Apache Commons IO 官方文档
  • Apache Commons Text 官方文档
  • Apache Tika 官方文档
  • Google Guava 官方文档
  • Hutool 官方文档
  • Jodd 官方文档
  • Spring Framework 官方 Javadoc
  • MyBatis 官方文档

Java 常用工具类深度盘点:从 JDK、Apache Commons 到 Guava、Hutool、Spring、MyBatis
https://allendericdalexander.github.io/2026/06/07/java/utils/java_util/
作者
AtLuoFu
发布于
2026年6月7日
许可协议