封装Java常用集合工具类

日常写代码时,集合转换大概长这样:

1
2
3
4
List<Long> ids = userList.stream()
.filter(u -> u.getAge() >= 20)
.map(User::getId)
.collect(Collectors.toList());

但每个调用点都要写一遍 .stream() ... .collect(Collectors.toList()) 这串样板;userList 万一是 null,stream() 直接 NPE;list 转 map 时可能还需判空。所以一般都会沉淀一个自己的工具类,这里我的工具类叫 FunUtils。

完整代码

先上全貌,后面逐个方法说:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public final class FunUtils {

private FunUtils() {
}

// ---------- 元素类型转换 ----------

public static <T, R> List<R> toList(List<T> list, Function<T, R> mapper) {
return convert(list, mapper, Collectors.toList());
}

public static <T, R> Set<R> toSet(List<T> list, Function<T, R> mapper) {
return convert(list, mapper, Collectors.toSet());
}

/**
* 转换主干:null 安全,返回类型由 collector 决定
*/
private static <T, R, C extends Collection<R>> C convert(List<T> list,
Function<T, R> mapper, Collector<R, ?, C> collector) {
return emptyIfNull(list).stream().map(mapper).collect(collector);
}

// ---------- List 转 Map ----------

/** 严格版:value 为元素本身,key 重复直接抛 IllegalStateException */
public static <K, V> Map<K, V> toMap(List<V> list, Function<V, K> keyFunc) {
return toMap(list, keyFunc, v -> v);
}

/** 严格版:key、value 都指定,key 重复直接抛 IllegalStateException */
public static <T, K, V> Map<K, V> toMap(List<T> list,
Function<T, K> keyFunc, Function<T, V> valFunc) {
return emptyIfNull(list).stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(keyFunc, valFunc));
}

/** 宽容版:value 为元素本身,key 重复时后者覆盖前者 */
public static <K, V> Map<K, V> toMapQuietly(List<V> list, Function<V, K> keyFunc) {
return toMapQuietly(list, keyFunc, v -> v);
}

/** 宽容版:key、value 都指定,key 重复时后者覆盖前者 */
public static <T, K, V> Map<K, V> toMapQuietly(List<T> list,
Function<T, K> keyFunc, Function<T, V> valFunc) {
return emptyIfNull(list).stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(keyFunc, valFunc, (a, b) -> b));
}

public static <T, K> Map<K, List<T>> groupBy(List<T> list, Function<T, K> keyFunc) {
return emptyIfNull(list).stream()
.filter(Objects::nonNull)
.collect(Collectors.groupingBy(keyFunc));
}

// ---------- 过滤 ----------

public static <T> List<T> filter(List<T> list, Predicate<T> pred) {
return emptyIfNull(list).stream().filter(pred).collect(Collectors.toList());
}

public static <T> List<T> filterNotNull(List<T> list) {
return filter(list, Objects::nonNull);
}

/** 按指定 key 去重,保留先出现的元素 */
public static <T, K> List<T> distinctByKey(List<T> list, Function<T, K> keyFunc) {
Set<K> seen = new HashSet<>();
return filter(filterNotNull(list), t -> seen.add(keyFunc.apply(t)));
}

// ---------- 其他 ----------

public static <T> List<T> emptyIfNull(List<T> list) {
return list == null ? Collections.emptyList() : list;
}

public static <T> Optional<T> findFirst(List<T> list, Predicate<T> pred) {
return emptyIfNull(list).stream().filter(pred).findFirst();
}

public static <T> Optional<T> findAny(List<T> list, Predicate<T> pred) {
return emptyIfNull(list).stream().filter(pred).findAny();
}
}

逐个方法说

1. 元素类型转换

toList

项目 说明
签名 <T, R> List<R> toList(List<T> list, Function<T, R> mapper)
作用 每个元素经 mapper 转换后收集成 List
null 处理 list 为 null 时返回空 List
示例 FunUtils.toList(users, User::getName)

toSet

项目 说明
签名 <T, R> Set<R> toSet(List<T> list, Function<T, R> mapper)
作用 每个元素经 mapper 转换后收集成 Set,自动按 equals 去重
null 处理 list 为 null 时返回空 Set
示例 FunUtils.toSet(users, User::getId)

设计上只有私有的 convert 一个方法干实事,toListtoSet 只是往里传不同的 Collector。哪天想返回 LinkedHashSet 或别的容器,加几行新方法就行。

2. List 转 Map

toMap(严格版)

项目 说明
签名① <K, V> Map<K, V> toMap(List<V> list, Function<V, K> keyFunc)
签名② <T, K, V> Map<K, V> toMap(List<T> list, Function<T, K> keyFunc, Function<T, V> valFunc)
作用 签名① value 为元素本身;签名② key、value 都指定
null 处理 list 为 null 返回空 Map;null 元素会被先过滤掉
示例 FunUtils.toMap(users, User::getId)FunUtils.toMap(users, User::getId, User::getName)
重复 key IllegalStateException

重复 key 就炸,是有意为之。

1
2
// 列表里有两个 id=2 的用户 → IllegalStateException: Duplicate key
Map<Long, User> byId = FunUtils.toMap(users, User::getId);

toMapQuietly(宽容版)

项目 说明
签名① <K, V> Map<K, V> toMapQuietly(List<V> list, Function<V, K> keyFunc)
签名② <T, K, V> Map<K, V> toMapQuietly(List<T> list, Function<T, K> keyFunc, Function<T, V> valFunc)
作用 同 toMap 两个重载,仅重复 key 行为不同
null 处理 同 toMap
示例 FunUtils.toMapQuietly(users, User::getId, User::getName)
重复 key 后者覆盖前者,不抛异常

确认重复 key 无所谓(或者明确要”取最新”)时用这个,内部就是多传了一个 (a, b) -> b 的 merge 函数。

一个所有 toMap 系方法共用的提醒:Collectors.toMap 的 value 不能是 null(内部走 HashMap.merge)。

groupBy

项目 说明
签名 <T, K> Map<K, List<T>> groupBy(List<T> list, Function<T, K> keyFunc)
作用 按 key 分桶,value 是同 key 的元素列表
null 处理 list 为 null 返回空 Map;null 元素会被先过滤掉
示例 FunUtils.groupBy(users, u -> u.getAge() / 10)(按年龄段分桶)

3. 过滤

filter

项目 说明
签名 <T> List<T> filter(List<T> list, Predicate<T> pred)
作用 保留满足条件的元素
null 处理 list 为 null 时返回空 List
示例 FunUtils.filter(users, u -> u.getAge() >= 18)

注意 filter 本身不过滤 null 元素。稳妥起见先 filterNotNull 再 filter。

filterNotNull

项目 说明
签名 <T> List<T> filterNotNull(List<T> list)
作用 去掉 null 元素
null 处理 list 为 null 时返回空 List
示例 FunUtils.filterNotNull(userList)

distinctByKey

项目 说明
签名 <T, K> List<T> distinctByKey(List<T> list, Function<T, K> keyFunc)
作用 按指定 key 去重,保留先出现的元素
null 处理 list 为 null 时返回空 List;null 元素会被先过滤掉
示例 FunUtils.distinctByKey(users, User::getId)

JDK 自带的 distinct() 只能按 equals 去重,按字段去重得自己来。套路是拿一个 Set 记录出现过的 key,filter 里靠 Set.add 的返回值(false 表示已存在)把重复的挡掉。实现里先 filterNotNull 再去重,不然 null 元素在 keyFunc.apply 时就 NPE 了。

4. 顺手的小工具

emptyIfNull

项目 说明
签名 <T> List<T> emptyIfNull(List<T> list)
作用 null 转空集合
null 处理 list 为 null 时返回空 List,否则原样返回
示例 for (User u : FunUtils.emptyIfNull(userList)) { ... }

它是整个类的地基——converttoMapfilter 的 null 安全全靠它把 null 换成空集合。返回的空集合是 Collections.emptyList(),不可变,别往里 add。

findFirst

项目 说明
签名 <T> Optional<T> findFirst(List<T> list, Predicate<T> pred)
作用 取第一个满足条件的元素
null 处理 list 为 null 返回 Optional.empty()
示例 FunUtils.findFirst(users, u -> u.getAge() >= 18)

findAny

项目 说明
签名 <T> Optional<T> findAny(List<T> list, Predicate<T> pred)
作用 取任意一个满足条件的元素
null 处理 list 为 null 返回 Optional.empty()
示例 FunUtils.findAny(users, u -> u.getAge() >= 18)

两者在串行流里行为一致;findAny 的意义在并行流——不保证取第一个匹配的,随便挑一个,性能更好。一般用 findFirst 就够了,语义明确。

小结

整个工具类就是:公共主干收敛成私有方法,公开方法全是薄入口,减少重复代码。


封装Java常用集合工具类
https://zyue2022.github.io/2026/09/03/封装Java常用集合工具类-笔记/
作者
ZYUE
发布于
2026年9月3日
更新于
2026年9月4日
许可协议