开发利器Hutool之MapUtil的使用

154 阅读9分钟

image

一、概述

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 keyValueSeparatorkey 与 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);

image