云计算百科
云计算领域专业知识百科平台

19个方法搞定Map安全取值,嵌套数据不用层层判空强转

📌 高并发系统设计: 基于Redis与MQ的秒杀系统架构:库存预减、防超卖与限流防刷全流程解析

📋 目录

  • 一、工具类功能
  • 二、所需环境
  • 三、方法与调用示例(按需调用)
  • 四、工具类源码(可直接复制)

一、工具类功能

读写 Map 不用先判空、不用层层强转,取不到就按默认值兜底,另给构建、翻转、过滤、映射、合并与去 null 等常用动作,专治开放平台响应、配置中心这类嵌套数据。

二、所需环境

JDK 17或以上

三、方法与调用示例(按需调用)

🔢 方法序号:1

🎯 方法用途:

  • 从 Map 里取一个值,键不存在和键存在但值是 null 一并按缺失处理,落到默认值
  • 反序列化出来的 JSON、开放平台返回的 Map 里字段常是显式 null,用 JDK 的兜底接不住

🧩 方法定义:public static <K, V> V getOrDefault(Map<K, V> map, K key, V defaultValue)

🗣️ 入参解释:public static <K, V> V getOrDefault(源Map, 键, 默认值)

📤 返回值解释:V,键有非 null 值时给该值,否则给默认值

💡 边界口径:

  • 与 Map#getOrDefault 的差别就在 null 值这一条:JDK 只在键缺失时兜底,值为 null 时照样返回 null
  • 要 JDK 的原语义就直接写 map.getOrDefault(key, default)
  • 两者静态导入极易混淆,建议以 MapSafeUtils.getOrDefault 限定调用
  • map 传 null 不抛异常,直接给默认值

🧪 方法示例:

  • getOrDefault(payInfo, "channel", "未指定渠道") 拿到 未指定渠道,channel 这个键在但值是 null
  • getOrDefault(payInfo, "amount", 0L) 拿到 12850
  • getOrDefault(null, "score", "兜底") 拿到 兜底

🔢 方法序号:2

🎯 方法用途:

  • 按点分路径在嵌套 Map 里逐级下钻,任一层断裂直接落到默认值,不用写一串 instanceof 加 get
  • 接口返回体里 data.orderNo 这类两层以上的结构取值时用

🧩 方法定义:public static <V> V getByPath(Map<String, ?> root, String path, V defaultValue)

🗣️ 入参解释:public static <V> V getByPath(根Map, 点分路径, 默认值)

📤 返回值解释:V,路径走通给原值,走不通给默认值

💡 边界口径:

  • 分隔符固定为 .,键名本身含点时无法寻址,这类数据直接用嵌套 get
  • 中间层是字符串、集合等非 Map 时也判为断裂,直接兜底
  • 是无检查强转,实际类型与声明不符时在调用方的使用处抛 ClassCastException,要类型不符即兜底请用 getByPathAs
  • root 传 null 给默认值,path 为 null 或空白抛 IllegalArgumentException
  • 默认值不能传 Class,那样会静默返回该 Class 本身,这里直接拦掉

🧪 方法示例:

  • getByPath(response, "data.orderNo", "(缺失)") 拿到 SO20261005001
  • getByPath(null, "order.no", "根为null给默认") 拿到 根为null给默认
  • getByPath(dotted, "a.b", "取不到") 拿到 取不到,键名 a.b 整体含点时定位不到
  • getByPath(root, "order.no.deeper", "中间层不是Map") 拿到 中间层不是Map
  • getByPath(response, "data.orderNo", String.class) 抛 IllegalArgumentException

🔢 方法序号:3

🎯 方法用途:

  • 三参版的省略形态,取不到就给 null,后面还要自己判空时用
  • 打日志、临时排查嵌套结构里到底有没有这个字段时最省事

🧩 方法定义:public static <V> V getByPath(Map<String, ?> root, String path)

🗣️ 入参解释:public static <V> V getByPath(根Map, 点分路径)

📤 返回值解释:V,路径走通给原值,取不到返回 null

💡 边界口径:

  • 内部就是 getByPath(root, path, null),路径口径与无检查强转的坑与三参版一致
  • 分不出「没有这个键」和「键的值是 null」,两种都返回 null
  • 接收变量声明成 Object 再自己判断最稳妥

🧪 方法示例:

  • getByPath(response, "data.notExist.deep") 拿到 null
  • getByPath(response, "data.orderNo.deep") 拿到 null,中间层已经不是 Map
  • getByPath(response, "data.rate") 当 Object 用拿到 3.5
  • 同一个值赋给 String 变量,在使用处抛 ClassCastException

🔢 方法序号:4

🎯 方法用途:

  • 下钻取到值后先验类型,最终值不是期望类型时和路径断裂一样落到默认值
  • 嵌套结构里字段类型不稳定,省掉调用方自己 try-catch 类型转换异常

🧩 方法定义:public static <V> V getByPathAs(Map<String, ?> root, String path, Class<V> type, V defaultValue)

🗣️ 入参解释:public static <V> V getByPathAs(根Map, 点分路径, 期望类型, 默认值)

📤 返回值解释:V,命中且类型匹配给该值,否则给默认值

💡 边界口径:

  • 判定走 Class#isInstance,取到的值是 null 不算命中,直接给默认值
  • 期望 Number 时 Integer、Long、Double 都算命中
  • type 传 null 抛 NullPointerException,这条与路径的 IllegalArgumentException 是两套口径
  • 路径为空或空白同样抛 IllegalArgumentException

🧪 方法示例:

  • getByPathAs(response, "data.orderNo", String.class, "(非字符串)") 拿到 SO20261005001
  • getByPathAs(response, "data.orderNo", Long.class, -1L) 拿到 -1,同一个键换个期望类型就兜底
  • getByPathAs(outer, "code", Number.class, 0) 拿到 0,Integer 算 Number
  • getByPathAs(response, "data.orderNo", null, "x") 抛 NullPointerException

🔢 方法序号:5

🎯 方法用途:

  • 把可能为 null 的 Map 变成能直接遍历、能直接取 size 的 Map,省掉每个调用点的判空
  • 上游字段经常整块缺失的响应体,先过一道再往下走

🧩 方法定义:public static <K, V> Map<K, V> nullSafe(Map<K, V> map)

🗣️ 入参解释:public static <K, V> Map<K, V> nullSafe(可空Map)

📤 返回值解释:Map<K, V>,入参为 null 给不可变空 Map,非 null 原对象返回

💡 边界口径:

  • 兜底用的是 Collections.emptyMap(),往它写入抛 UnsupportedOperationException,要写就自己 new HashMap<>(…)
  • 非 null 时不复制,返回的就是同一个对象,改它等于改源
  • 只兜 Map 本身,里面的值仍可能是 null

🧪 方法示例:

  • nullSafe(null) 拿到 size=0 的空 Map
  • 往 nullSafe(null) 的结果里 put 抛 UnsupportedOperationException
  • nullSafe(data) == data 拿到 true,非 null 时不复制

🔢 方法序号:6

🎯 方法用途:

  • 装一对键值成 Map,null 键与 null 值都收,返回的是可变 LinkedHashMap
  • 给下游传参、造小结构时替代 Collections.singletonMap,后者不允许改

🧩 方法定义:public static <K, V> Map<K, V> of(K k1, V v1)

🗣️ 入参解释:public static <K, V> Map<K, V> of(键, 值)

📤 返回值解释:Map<K, V>,含这一对键值的可变 Map

💡 边界口径:

  • Map.of 遇到 null 键或 null 值直接抛异常,这里照样收进 Map
  • Map.of 返回不可变且不保证顺序,本方法返回可变且按插入顺序迭代
  • 每次调用新建一个 Map,不共享结构

🧪 方法示例:

  • 往 of("channel", "ALIPAY") 的结果再放一项拿到 {channel=ALIPAY, extra=null},值为 null 也放得进去
  • of(null, null) 拿到 {null=null},size 为 1

🔢 方法序号:7

🎯 方法用途:

  • 一次放两对键值,仍是可变的插入顺序 Map
  • 造两条以上的小数据、给断言准备期望 Map 时用,比 new 再 put 短

🧩 方法定义:public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2)

🗣️ 入参解释:public static <K, V> Map<K, V> of(键1, 值1, 键2, 值2)

📤 返回值解释:Map<K, V>,含两对键值的可变 Map

💡 边界口径:

  • 迭代顺序就是写入顺序,这一点和 Map.of 的无序不同
  • 两对键相同(equals 相等)时后写的覆盖先写的,只剩一条
  • 需要三对以上先拿两对版再 put,或直接用 merge

🧪 方法示例:

  • of("b", 2, "a", 1).keySet() 拿到 [b, a],JDK 的 Map.of 给不了这个稳定顺序

🔢 方法序号:8

🎯 方法用途:

  • 把 key 与 value 对调,比如「码到名」的字典翻成「名到码」再按名字反查
  • 结果是新 Map,源 Map 保持不动

🧩 方法定义:public static <K, V> Map<V, K> invert(Map<K, V> source)

🗣️ 入参解释:public static <K, V> Map<V, K> invert(源Map)

📤 返回值解释:Map<V, K>,键值互换后的可变 LinkedHashMap

💡 边界口径:

  • 原 value 有重复时后出现的 key 覆盖先出现的,只留一条映射
  • 要保留全部映射(一个值对多个键)请改用 ListToMapUtils 的 groupValueBy
  • value 为 null 时它会被当成键放进结果里
  • 原 value 必须能当 Map 键,数组、未重写 equals 的对象翻过去也查不到
  • source 传 null 抛 IllegalArgumentException

🧪 方法示例:

  • invert(codeToName) 拿到 {待支付=10, 已支付=20},按名字找码就靠它
  • invert(dup) 拿到 {A组=李四},张三李四同组时后者覆盖
  • invert(nullValueMap) 拿到 {91=甲, null=乙}
  • invert(null) 抛 IllegalArgumentException

🔢 方法序号:9

🎯 方法用途:

  • 按值筛 Map,谓词为 true 的条目留下,顺序按原 Map
  • 只留及格分、只留某个状态这类按值裁剪时用

🧩 方法定义:public static <K, V> Map<K, V> filterByValue(Map<K, V> source, Predicate<? super V> predicate)

🗣️ 入参解释:public static <K, V> Map<K, V> filterByValue(源Map, 值谓词)

📤 返回值解释:Map<K, V>,过滤后的可变 LinkedHashMap,保持原顺序

💡 边界口径:

  • 谓词会收到 null 值,判空得写在谓词里,本类不替调用方挡
  • 不回写入参,返回新建的 Map,条目里的值仍是同一个引用
  • source 或 predicate 为 null 抛 IllegalArgumentException

🧪 方法示例:

  • filterByValue(scores, score -> score >= 80) 拿到 {张三=91}
  • filterByValue(withNullValue, v -> v != null && v > 80) 拿到 {甲=91},值为 null 的项由谓词自己挡掉
  • filterByValue(scores, null) 抛 IllegalArgumentException

🔢 方法序号:10

🎯 方法用途:

  • 按键筛 Map,键满足谓词才留,值不看
  • 按前缀挑配置项、只留某个人的字段这类操作用这一版

🧩 方法定义:public static <K, V> Map<K, V> filterByKey(Map<K, V> source, Predicate<? super K> predicate)

🗣️ 入参解释:public static <K, V> Map<K, V> filterByKey(源Map, 键谓词)

📤 返回值解释:Map<K, V>,过滤后的可变 LinkedHashMap,保持原顺序

💡 边界口径:

  • 实现是先整体拷贝再按键 removeIf,返回新 Map,源不动
  • 值为 null 的条目只要键满足条件就照样留下
  • source 或 predicate 为 null 抛 IllegalArgumentException

🧪 方法示例:

  • filterByKey(scores, name -> name.contains("张")) 拿到 {张三=91}

🔢 方法序号:11

🎯 方法用途:

  • 键保持不动,把每个值换成另一种类型或另一种表达,比如分数换成评语、码值换成中文名
  • 结果给前端展示、日志输出这类需要「换个说法」的场合

🧩 方法定义:public static <K, V, R> Map<K, R> mapValue(Map<K, V> source, Function<V, R> mapper)

🗣️ 入参解释:public static <K, V, R> Map<K, R> mapValue(源Map, 值映射函数)

📤 返回值解释:Map<K, R>,键不变、值换成映射结果的新 Map

💡 边界口径:

  • 映射函数会收到 null 值,不判空就在函数里接住
  • 返回新 Map,键与映射结果是新建的,源 Map 不受影响
  • source 或 mapper 为 null 抛 IllegalArgumentException

🧪 方法示例:

  • mapValue(scores, score -> score >= 60 ? "及格" : "不及格") 拿到 {张三=及格, 李四=不及格, 王五=及格}
  • mapValue(nullValueMap, v -> v == null ? "无成绩" : v + "分") 拿到 {甲=91分, 乙=无成绩}

🔢 方法序号:12

🎯 方法用途:

  • 一步判「是 null 或者没有条目」,写前置校验时不用先判空引用再判 size
  • 参数里传 Map 的方法进门先问一句,后面就能放心遍历

🧩 方法定义:public static boolean isEmpty(Map<?, ?> map)

🗣️ 入参解释:public static boolean isEmpty(待判断Map)

📤 返回值解释:boolean,为 null 或没有任何条目返回 true

💡 边界口径:

  • 只看条目数,不看内容:装着 null 键、null 值的 Map 不算空
  • 没有提供反向的 isNotEmpty,需要就自己取反

🧪 方法示例:

  • isEmpty(null) 拿到 true
  • isEmpty(Map.of()) 拿到 true
  • isEmpty(response) 拿到 false
  • isEmpty(of(null, null)) 拿到 false,装着 null 键也算有内容

🔢 方法序号:13

🎯 方法用途:

  • 多个配置 Map 叠成一层,后者覆盖前者的同键,比如默认配置加环境覆盖加请求覆盖
  • 参数数组里混着 null Map 时不用先过滤,本方法自己跳过

🧩 方法定义:public static <K, V> Map<K, V> merge(Map<? extends K, ? extends V>… maps)

🗣️ 入参解释:public static <K, V> Map<K, V> merge(若干Map按覆盖顺序)

📤 返回值解释:Map<K, V>,合并后的可变 LinkedHashMap

💡 边界口径:

  • 键的顺序按首次出现的位置排,后面覆盖同键不改它的位置
  • 至少传一个 Map,merge() 空参抛 IllegalArgumentException;数组本身为 null 同样报错
  • 结果是新建的 Map,条目里的值仍是同一个引用

🧪 方法示例:

  • merge(base, null, override) 拿到 {timeout=3000, retry=3, signType=RSA},retry 被后者改成 3
  • merge(Map.of("timeout", 1000), null, Map.of("timeout", 3000, "retry", 3)) 拿到 {timeout=3000, retry=3}
  • merge(outer) 只给一个 Map 时拿到它的一份拷贝 {code=0, data={score=98, rate=3.5, code=0}}
  • merge() 抛 IllegalArgumentException

🔢 方法序号:14

🎯 方法用途:

  • 从 Map 里取 int,键缺失、值为 null、值不是 Number 三种情况一律给默认值
  • 外部报文里的数字字段类型不稳时用,比强转 Integer 安全

🧩 方法定义:public static int getInt(Map<String, ?> map, String key, int defaultValue)

🗣️ 入参解释:public static int getInt(源Map, 键, 默认值)

📤 返回值解释:int,值是 Number 时取 intValue(),否则给默认值

💡 边界口径:

  • 只认 Number,字符串 “98” 不当数字解析,不做 Integer.parseInt 那套
  • Double 走 intValue() 会截掉小数
  • Long 超出 int 范围时按 intValue() 截低位,不会抛异常,取值前先确认量级
  • map 传 null 给默认值

🧪 方法示例:

  • getInt(response, "code", -1) 拿到 0,值真是 0 时与默认值 -1 分得清
  • getInt(data, "score", -1) 拿到 -1,值是字符串就不解析
  • getInt(nums, "rate", -1) 拿到 3,Double 3.5 被截掉小数
  • getInt(nums, "big", -1) 拿到 410065408,9000000000 超出 int 后截了低位
  • getInt(null, "rate", -1) 拿到 -1

🔢 方法序号:15

🎯 方法用途:

  • 从 Map 里取 long,金额分、时间戳这类字段用这一版,口径与 getInt 一致
  • 值可能是 Integer 也可能是 Long 的接口字段,取成 long 不丢位

🧩 方法定义:public static long getLong(Map<String, ?> map, String key, long defaultValue)

🗣️ 入参解释:public static long getLong(源Map, 键, 默认值)

📤 返回值解释:long,值是 Number 时取 longValue(),否则给默认值

💡 边界口径:

  • Integer 升到 long 无损,Double 会截掉小数
  • 字符串不解析,缺键给默认值
  • map 传 null 给默认值

🧪 方法示例:

  • getLong(payInfo, "amount", 0L) 拿到 12850
  • getLong(payInfo, "discount", 0L) 拿到 0,键不存在时兜底
  • getLong(nums, "score", -1L) 拿到 98,Integer 升 long 无损

🔢 方法序号:16

🎯 方法用途:

  • 从 Map 里取 double,费率、比例这类字段用,缺键与类型不符一律兜底
  • 值有时是整数写法有时是小数时用,Integer 也能直接取成小数形态

🧩 方法定义:public static double getDouble(Map<String, ?> map, String key, double defaultValue)

🗣️ 入参解释:public static double getDouble(源Map, 键, 默认值)

📤 返回值解释:double,值是 Number 时取 doubleValue(),否则给默认值

💡 边界口径:

  • 只认 Number,字符串 “3.5” 不算
  • Integer、Long 取出来会变成 98.0 这种形态,输出前注意格式
  • 金额精度要求高时别用 double,取 long 分值自己算

🧪 方法示例:

  • getDouble(data, "rate", 1.0d) 拿到 3.5
  • getDouble(nums, "score", -1D) 拿到 98.0,整数值被转成小数形态

🔢 方法序号:17

🎯 方法用途:

  • 从 Map 里取 boolean,值必须是 Boolean 实例,其余一律给默认值
  • 开关位、是否已支付这类字段,用默认值表达「没给就按某种默认处理」

🧩 方法定义:public static boolean getBoolean(Map<String, ?> map, String key, boolean defaultValue)

🗣️ 入参解释:public static boolean getBoolean(源Map, 键, 默认值)

📤 返回值解释:boolean,值是 Boolean 时取该值,否则给默认值

💡 边界口径:

  • 字符串 “true” 不当 true 处理,"1" 也不当 true,缺键与 null 值同样走默认
  • Integer 0 与 1 不会被解释成 false 与 true
  • map 传 null 给默认值

🧪 方法示例:

  • getBoolean(data, "paid", false) 拿到 true
  • getBoolean(data, "orderNo", false) 拿到 false,值是字符串就当取不到

🔢 方法序号:18

🎯 方法用途:

  • 从 Map 里取一段文本,缺失与 null 给默认值,非 String 值走 String.valueOf
  • 拼日志、拼提示语时不管值实际是什么类型都能拿到一个字符串

🧩 方法定义:public static String getString(Map<String, ?> map, String key, String defaultValue)

🗣️ 入参解释:public static String getString(源Map, 键, 默认值)

📤 返回值解释:String,值非 null 时给它的文本形态,null 或缺失给默认值

💡 边界口径:

  • 数字直接转文本,Long 12850 拿到 “12850”
  • 数组、没重写 toString 的对象只会得到默认 toString,别指望它做格式化
  • map 传 null 给默认值,值为 null 也给默认值

🧪 方法示例:

  • getString(data, "score", "") 拿到 "98"
  • getString(payInfo, "amount", "") 拿到 "12850"
  • getString(data, "absent", "默认文案") 拿到 默认文案
  • getString(of("arr", new int[]{1, 2}), "arr", "") 拿到 [I@14d3bc22,数组只能得到默认 toString

🔢 方法序号:19

🎯 方法用途:

  • 把值为 null 的条目清掉,剩下的按原顺序装进新 Map
  • 提交前清洗可选字段、序列化前不想带一堆 null 时用

🧩 方法定义:public static <K, V> Map<K, V> removeNullValues(Map<K, V> source)

🗣️ 入参解释:public static <K, V> Map<K, V> removeNullValues(源Map)

📤 返回值解释:Map<K, V>,去掉 null 值条目后的可变 LinkedHashMap

💡 边界口径:

  • 只清值为 null 的项,空字符串、空集合原样保留
  • null 键本身不会被清,清的只是值为 null 的条目
  • 源 Map 不动,返回新建的 Map,值仍是同一个引用
  • source 传 null 抛 IllegalArgumentException

🧪 方法示例:

  • removeNullValues(mixed) 拿到 剩 3 项,值为 null 的 channel 被摘掉
  • removeNullValues(blanks) 拿到 {name=, tags=[]},空串与空集合都留着
  • removeNullValues(Map.of()) 拿到 {}

四、工具类源码(可直接复制)

📦 源码文件

赞(0)
未经允许不得转载:网硕互联帮助中心 » 19个方法搞定Map安全取值,嵌套数据不用层层判空强转
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!