一、概述
1.1 工具简介
MapUtil 是 Hutool 中用于简化 Map 操作的工具类,它封装了大量静态方法,统一封装 Map 的创建、判断、转换、过滤、排序、合并、遍历等高频操作,替代冗长的原生 JDK 模板代码,减少重复、降低出错率。
1.2 引入依赖
在使用Hutool工具之前,我们需要将Hutool添加到项目的依赖中。如果使用Maven构建项目,可以在 pom.xml 文件中添加以下依赖:
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<!-- 请检查最新版本,可以根据实际需要进行调整 -->
<version>5.8.40</version>
</dependency>
Hutool-all 是一个 Hutool 的集成打包产品,由于考虑到"懒人"用户及分不清各个模块作用的用户,"无脑"引入 hutool-all 模块是快速开始和深入应用的最佳方式。如果你想像 SpringBoot 一样引入 Hutool,再由子模块决定用到哪些模块,你可以在父模块中加入:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-bom</artifactId>
<version>${hutool.version}</version>
<type>pom</type>
<!-- 注意这里是import -->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
然后再在子模块中就可以引入自己需要的模块了:
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
</dependencies>
二、Map 创建与初始化
2.1 链式构建 builder
builder 是 Hutool 的建造者模式链式构建 Map,底层返回 MapBuilder 对象,告别多次 new HashMap + 多次 put,代码更简洁,常用于接口参数组装、返回结果封装、测试构造数据。
| 方法 |
简要说明 |
| builder() |
空构建器,默认底层是 HashMap |
| builder(K k, V v) |
初始化携带一个键值对 |
| builder(Map<K,V> map) |
基于已有Map继续追加put(包装原有Map) |
返回值都是 MapBuilder,支持链式调用。
| 方法 |
简要说明 |
| put(K key, V value) |
添加键值对,返回自身,支持链式 |
| putAll(Map map) |
批量加入另一个 Map 全部元素 |
| build() |
构建返回 Map 实例(默认 HashMap,可修改) |
| buildImmutable() |
返回的 Map 不能新增、修改、删除 key,适合常量、返回结果防止外部篡改 |
// 简单用法
MapUtil.builder().put("name", "张三").put("age", 22).build();
// 使用 builder 链式创建,非常直观
Map<String, Object> params = MapUtil.builder(new HashMap<String, Object>())
.put("appId", "123456")
.put("timestamp", System.currentTimeMillis())
.build();
// builder 带初始键值对
MapUtil.builder("count", 100).put("total", 200).build();
// 基于已有 Map 追加元素
MapUtil.builder(MapUtil.of("a", "1")).put("b", "2").build();
2.2 快捷填充 of
由键值对数组创建,适合快速构造小规模的键值对
| 方法 |
返回类型 |
简要说明 |
| of(K key, V value) |
HashMap |
创建单个键值对的 HashMap |
| of(K key, V value, boolean isOrder) |
HashMap LinkedHashMap |
单个键值对,isOrder=true 返回 LinkedHashMap,保留插入顺序 |
| of(Object\[\] array) |
HashMap<Object,Object> |
二维字符串数组构建 Map |
| ofEntries(Map.Entry... entries) |
|
根据 Entry 数组创建 Map |
// 单个键值对
Map<String, String> single = MapUtil.of("key", "value");
// 二维数组构建
Map<Object, Object> colors = MapUtil.of(new String[][]{
{"RED", "#FF0000"},
{"GREEN", "#00FF00"}
});
Map<String,Integer> map = MapUtil.ofEntries(
Map.entry("a",1),
Map.entry("b",2)
);
2.3 快速创建
| 方法 |
返回类型 |
简要说明 |
| newConcurrentHashMap() |
ConcurrentHashMap |
无参创建线程安全 Map,并发场景使用 |
| newConcurrentHashMap(int size) |
ConcurrentHashMap |
指定初始容量 |
| newConcurrentHashMap(Map map) |
ConcurrentHashMap |
|
| newHashMap() |
HashMap |
新建空HashMap,无序 |
| newHashMap(boolean isLinked) |
HashMap LinkedHashMap |
isLinked=true 返回 LinkedHashMap,保插入序 |
| newHashMap(int size) |
HashMap |
指定初始容量,避免扩容(最多装 size 个) |
| newHashMap(int size, boolean isLinked) |
|
指定初始容量、是否有序 |
| newIdentityMap(int size) |
Map |
按引用相等判 key |
| newTreeMap(Comparator comparator) |
TreeMap |
自定义排序规则建 Map |
| newTreeMap(Map map, Comparator comparator) |
TreeMap |
已有 Map + 规则 |
| createMap(Class mapType) |
Map |
反射创建任意 Map 实现类 |
// 普通hashmap
Map<String, Object> hashMap = MapUtil.newHashMap(5);
hashMap.put("a", 1);
hashMap.put("b", 2);
// LinkedHashMap 有序
Map<String, Object> linkedMap = MapUtil.newHashMap(5, true);
linkedMap.put("z", 1);
linkedMap.put("a", 2);
System.out.println(linkedMap);
// TreeMap 按键排序
TreeMap<String, Integer> treeMap = MapUtil.newTreeMap(Comparator.reverseOrder());
treeMap.put("a", 1);
treeMap.put("b", 2);
System.out.println(treeMap); // {b=2,a=1}
// 并发安全map
ConcurrentHashMap<String, Object> concurrentMap = MapUtil.newConcurrentHashMap(10);
concurrentMap.put("k1", "v1");
三、Map 基础操作
3.1 判空与空值处理
处理 Map 的第一步,永远是先看看这个 Map 靠不靠谱。一个 null 的 Map,或者一个空的 Map,如果不加判断就直接操作,NullPointerException 就是"见面礼",MapUtils 给了我们几把趁手的"安检仪"。
| 方法 |
简要说明 |
| isEmpty(Map) |
判断 Map 为 null 或无元素 |
| isNotEmpty(Map) |
判断 Map 非空,用在需要 Map 有内容时才执行操作的场景 |
| emptyIfNull |
如果传入的map为null,就返回一个空的、不可变的EmptyMap对象,否则原样返回 |
| defaultIfEmpty |
若 Map 为空则返回默认 Map |
| empty() |
返回一个空 Map |
Map<String, Object> map = null;
System.out.println("Map 是否为空:" + MapUtil.isEmpty(map));
System.out.println("Map 是否不为空:" + MapUtil.isNotEmpty(map));
// null 安全:返回不可变空 Map,后续可放心遍历
Map<String, Object> safe = MapUtil.emptyIfNull(map);
// 为空则给默认值
Map<String, Object> queryResult = null;
Map<String, Object> fallbackMap = null;
Map<String, Object> data = MapUtil.defaultIfEmpty(queryResult, fallbackMap);
3.2 安全取值
这是 MapUtil 最实用的部分------直接从 Map 里取出指定类型,支持自动类型转换和默认值。如果 key 不存在或转换失败,通常返回 null 或默认值。提供默认值这个功能,看似简单,实则极大地提升了代码的鲁棒性,特别是在处理配置项或者非必填的用户输入时。
| 方法 |
简要说明 |
| getStr(Map map, Object key) |
取字符串值 |
| getStr(Map map, Object key, Boolean defaultValue) |
取字符串值 |
| getAny(Map map, K... keys) |
从原 Map 中提取指定的键值对,生成一个新的 map |
Map<String, Object> user = MapUtil.<String, Object>builder()
.put("name", "张三")
.put("age", "18") // 注意:字符串
.put("balance", "99.50")
.put("vip", "true")
.put("loginTime", "2024-05-01 12:30:00")
.build();
String name = MapUtil.getStr(user, "name");
Integer age = MapUtil.getInt(user, "age");
Long uid = MapUtil.getLong(user, "uid");
Double bal = MapUtil.getDouble(user, "balance");
Boolean vip = MapUtil.getBool(user, "vip");
Date login = MapUtil.getDate(user, "loginTime");
Map<String,Object> origin = MapUtil.of("id",1,"name","hutool","status",1);
Map<String,Object> subMap = MapUtil.getAny(origin, "id", "name");
3.3 获取指定类型
对于自定义对象或泛型集合,可以使用 get 配合 Class 或 TypeReference。
| 方法 |
简要说明 |
| get(Map map, Object key, Class type) |
泛型类型转换,复杂对象 |
| get(Map map, Object key, Class type, T defaultValue) |
|
| get(Map map, Object key, TypeReference type) |
|
| get(Map map, Object key, TypeReference type, T defaultValue) |
|
// 获取 User 对象
User user = MapUtil.get(map, "user", User.class);
// 获取 List<String>(利用 TypeReference 保留泛型信息)
List<String> tags = MapUtil.get(map, "tags", new TypeReference<List<String>>() {});
3.4 Map 元素操作
| 方法 |
简要说明 |
| clear(Map... maps) |
批量清空多个 map |
| removeAny(Map map, K... keys) |
移除指定 key(修改原 Map) |
| removeNullValue(Map map) |
移除 value 为 null 的项 |
| renameKey(Map map, K oldKey, K newKey) |
重命名 key,将旧 key 的值迁移到新 key |
MapUtil.removeAny(map, "a", "c"); // 移除指定 key(修改原 Map)
MapUtil.removeNullValue(map); // 移除 value 为 null 的项
四、Map 进阶操作
4.1 Map 排序
| 方法 |
简要说明 |
| sort(Map map) |
按 key 升序排序,返回新 Map,不修改原对象 |
| sort(Map map, Comparato comparator) |
按自定义比较器排序 |
| sortByValue(Map map, boolean isDesc) |
按 value 排序,可选是否倒序,返回新 Map,不修改原对象 |
@Test
public void sortTest() {
Map<String, Integer> score = MapUtil.newHashMap();
score.put("Chinese", 88);
score.put("Math", 95);
score.put("English", 70);
// 按 key 自然顺序排序(返回新 Map,原 Map 不变)
Map<String, Integer> byKey = MapUtil.sort(score);
System.out.println(byKey);
// 按 key 自定义比较器
Map<String, Integer> byKeyDesc = MapUtil.sort(score, Comparator.reverseOrder());
System.out.println(byKeyDesc);
}
@Test
public void sortByValueTest() {
Map<String, String> map = MapUtil.newHashMap();
map.put("a", "1");
map.put("b", "2");
map.put("c", "1");
// 排序, 不会改变原来的值
Map<String, String> newMap = MapUtil.sortByValue(map, true);
System.out.println(newMap);
}
4.2 map 拼接字符串
| 方法 |
简要说明 |
| join(map, separator, keyValueSeparator, isIgnoreNull) join(map, separator, keyValueSeparator, isIgnoreNull,otherParams) |
将map转成字符串,按遍历顺序拼接,顺序取决于 Map 实现 |
| joinIgnoreNull(map, separator, keyValueSeparator,otherParams) |
将map转成字符串,忽略null的键和值 |
| sortJoin(map, separator, keyValueSeparator, isIgnoreNull,otherParams) |
根据参数排序后拼接为字符串,常用于接口参数签名 |
参数含义:
| 方法 |
简要说明 |
| Map map |
待拼接的 Map |
| String separator |
条目之间的分隔符 |
| String keyValueSeparator |
key 与 value 之间的分隔符 |
| boolean isIgnoreNull |
是否忽略 null 键或值 |
| String... otherParams |
可变参数,拼在最后(通常放 appSecret) |
Map<String, String> params = MapUtil.builder(new HashMap<String, String>())
.put("key1", "value1")
.put("key3", "value3")
.put("key2", "value2")
.build();
String str01 = MapUtil.join(params, "&", "=");
System.out.println(str01);
// sortJoin:先按 key 排序再拼接 ------ 签名就用这个
String str02 = MapUtil.sortJoin(params, StrUtil.EMPTY, StrUtil.EMPTY, false);
System.out.println(str02);
// sortJoin 带尾部密钥
String str03 = MapUtil.sortJoin(params, "&", "=", false, "SECRET_KEY");
System.out.println(str03);
// joinIgnoreNull:跳过 null 的 key 和 value
String str04 = MapUtil.joinIgnoreNull(params, "&", "=");
System.out.println(str04);
4.3 filter 筛选元素
传入一个 Filter 实现,只保留满足条件的 Entry
| 方法 |
简要说明 |
| filter(Map map, K... keys) |
过滤Map保留指定键值对,如果键不存在跳过 |
| filter(Map map, Filter filter) |
自定义过滤,保留满足条件的元素,返回新 Map |
Map<String, String> map = MapUtil.newHashMap();
map.put("a", "1");
map.put("b", "2");
map.put("c", "3");
map.put("d", "4");
// 过滤出被2整除的数据
Map<String, String> map2 = MapUtil.filter(map, t -> Convert.toInt(t.getValue()) % 2 == 0);
System.out.println(map2);
五、Map 转换操作
5.1 键值互换
| 方法 |
简要说明 |
| inverse(Map map) |
键值互换,注意 value 重复会覆盖,在需要根据值反向查找键的时候特别有用 |
✅ 如果原 Map 有重复 value ,互换后会发生覆盖,条目数变少。反转前先确认 value 唯一,或者改用 Map<K, List<V>> 结构。
@Test
public void inverseTest() {
Map<String, String> map = MapUtil.newHashMap();
map.put("a", "1");
map.put("b", "2");
map.put("c", "1");
System.out.println("键值互换之前:" + map);
Map<String, String> inverse = MapUtil.inverse(map);
System.out.println("键值互换之后:" + inverse);
}
@Test
public void invertMapTest() {
// 错误码映射
Map<Integer, String> errorCodeMap = new HashMap<>();
errorCodeMap.put(404, "Not Found");
errorCodeMap.put(500, "Internal Server Error");
errorCodeMap.put(200, "Success");
// 反向查找:通过错误信息找错误码
Map<String, Integer> invertedMap = MapUtil.inverse(errorCodeMap);
String receivedMessage = "Internal Server Error";
Integer possibleErrorCode = invertedMap.get(receivedMessage);
if (possibleErrorCode != null) {
System.out.println("对应的错误码是: " + possibleErrorCode); // 输出 500
}
}
5.2 行列转换
大家是不是遇到过将多个 map 以 key 进行分组,然后相同 key 下的 value 合并成一个 list 的场景,现在可以通过下面的行列互转的方式轻松解决。
| 方法 |
简要说明 |
备注 |
| toListMap(Iterable mapList) |
行转列,按相同的 key 进行分组,把 value 合并成一个 List |
生成新 Map |
| toMapList(Map listMap) |
列转行,将键值对为 List 的 Map 还原为 Map 列表 |
生成新 Map |
@Test
public void toListMapTest() {
Map<String, String> map1 = MapUtil.of("a", "1");
Map<String, String> map2 = MapUtil.of("a", "5");
Map<String, String> map3 = MapUtil.of("b", "4");
Map<String, String> map4 = MapUtil.of("a", "5");
List<Map<String, String>> maps = ListUtil.of(map1, map2, map3, map4);
System.out.println("toListMap: " + MapUtil.toListMap(maps));
}
@Test
public void toMapListTest() {
Map<String, List<Integer>> cols = MapUtil.newHashMap(true);
cols.put("a", List.of(1, 2, 3, 4));
cols.put("b", List.of(1, 2, 3));
cols.put("c", List.of(1));
List<Map<String, Integer>> rows = MapUtil.toMapList(cols);
System.out.println(rows);
}
5.3 结构变换
| 方法 |
作用 |
是否原地修改 |
| grouping(Iterable entries) |
按 entry 的 key 分组,值聚合成 List |
新 Map |
| toCamelCaseMap(Map map) |
key 下划线转驼峰 |
新 Map |
| toObjectArray(Map map) |
Map → 二维数组 |
新数组 |
| valuesOfKeys(Map map, Iterator keys) |
批量按 key 取值,返回 List(缺则 null) |
新 List |
| partition(Map, int size) |
大 Map 按大小切分为多个小HashMap |
新 List |
| flatten(Map) |
多层嵌套 Map 摊平为一层(递归) |
新 Map |
| map(Map map, BiFunction biFunction) |
使用 BiFunction 对每个 Entry 进行转换 |
新 Map |
MapUtil.partition(MapUtil.of(new Object[][]{{"a",1},{"b",2},{"c",3}}), 2);
Map<String, Object> nested = MapUtil.<String,Object>builder()
.put("k", 1)
.put("sub", MapUtil.of("x", 2))
.build();
MapUtil.flatten(nested); // → {x=2, k=1}
Map<String, String> map = MapUtil.newHashMap();
map.put("a", "1");
map.put("b", "2");
Map<String, String> result = MapUtil.map(map, (k, v) -> k.toUpperCase() + "-" + v);
System.out.println(result);