Kotlin × Gson:为什么遍历 JsonObject 要用 entrySet()

理解 entrySet() 和 keySet() 的区别,掌握高效遍历与 JSON 合并技巧。

一、前言

在日常 Android 或 Kotlin 后端开发中,我们经常需要操作 JSON 对象,例如:

Kotlin 复制代码
val mergedData = JsonObject().apply {
    originalMessageObject.entrySet().forEach { entry ->
        add(entry.key, entry.value)
    }
    route.extraData?.entrySet()?.forEach { entry ->
        add(entry.key, entry.value)
    }
}

很多同学会问:

为什么这里要用 .entrySet()?我直接 .keySet() 不行吗?

别看这只是一个小细节,但它直接影响性能、语义清晰度,以及你代码的"专业感"。

这篇文章我们就来彻底讲清楚。

二、Map / JsonObject 的三种遍历方式

Gson 的 JsonObject 本质上是一个 Map<String, JsonElement>,因此遍历方式和 Map 一样:

遍历方式 能拿到什么 性能 适用场景
keySet() 只能拿到 key ⚠️ 需要再 get(key) 查一次 value 只需要 key 时
values() 只能拿到 value ✅ 一次遍历 只需要 value 时
entrySet() 一次拿到 key + value ✅ 高效 + 清晰 同时需要 key 与 value 时(最常用)

三、为什么 entrySet() 更推荐

1. 一次遍历同时拿 key 与 value

entrySet() 返回 Set<Map.Entry<K, V>>,

每个 entry 里已经包含 key 和 value,不需要再调用 get(key)。

Kotlin 复制代码
// ✅ 推荐:一次拿到 key + value
for ((key, value) in json.entrySet()) {
    merged.add(key, value)
}

// ❌ 不优:keySet + get(key),多一次查找
for (key in json.keySet()) {
    val value = json.get(key)
    merged.add(key, value)
}

虽然对小对象差别不大,但在大 JSON、频繁合并的场景下,多余的 get() 会产生上百次额外查表操作。

2. Kotlin 写法更优雅

Kotlin 支持对 Map.Entry 解构,因此:

Kotlin 复制代码
for ((key, value) in json.entrySet()) {
    println("$key = $value")
}

写法简洁直观,语义完全贴合"我在遍历键值对"的需求。

3. 性能对比(复杂度分析)

假设对象有 n 个字段:

操作 时间复杂度 说明
entrySet() O(n) 每次直接拿 key,value
keySet()+get(key) O(n) + n×O(1) 多执行一次查找,哈希表仍有额外开销

在 HashMap 这种结构中,虽然 get() 平均是 O(1),但每次都要进行哈希、比较、寻址操作。

对性能敏感的代码(如 TCP 消息解析、日志聚合)中,entrySet() 明显更合适。

四、JSON 合并的最佳实践

合并两个 JsonObject 是最常见的场景,比如 "原始消息 + 附加数据":

Kotlin 复制代码
val merged = JsonObject().apply {
    // 先复制原始对象
    for ((k, v) in original.entrySet()) add(k, v)
    // 再复制 extraData(同名 key 覆盖)
    for ((k, v) in extra.entrySet()) add(k, v)
}

如果希望"原始字段优先、extra 只补充缺失":

Kotlin 复制代码
val merged = JsonObject().apply {
    for ((k, v) in original.entrySet()) add(k, v)
    for ((k, v) in extra.entrySet()) if (!has(k)) add(k, v)
}

递归深度合并(嵌套对象)也同样基于 entrySet():

Kotlin 复制代码
fun mergeDeep(dst: JsonObject, src: JsonObject) {
    for ((k, v) in src.entrySet()) {
        val dv = dst.get(k)
        if (dv is JsonObject && v is JsonObject) mergeDeep(dv, v)
        else dst.add(k, v.deepCopy())
    }
}

五、什么时候用 keySet()

只有一种情况:你根本不需要 value。

Kotlin 复制代码
for (key in json.keySet()) {
    if (key.startsWith("_")) println("非法字段:$key")
}

或者做键集合的运算(交集、差集等)时。

只要你需要 value,首选 entrySet()。

只要你不需要 value,用 keySet() 更干净。

六、实践建议

  1. 固定习惯 :遍历 Map/Json 时默认用 entrySet()。

  2. 明确策略:合并时清楚覆盖逻辑(覆盖/补齐/深度合并)。

  3. 必要时深拷贝:避免 JsonElement 共享引用。

  4. 日志友好:当字段被覆盖时可打印提示,便于调试。

七、总结记忆表

目的 推荐写法 说明
遍历所有字段 for ((k, v) in json.entrySet()) 最常见用法
只看 key for (k in json.keySet()) 只需键名时
只看 value for (v in json.values()) 只需值时
复制对象 dst.add(k, v) 后写覆盖前写
保留原始 if (!dst.has(k)) dst.add(k, v) 只补充缺失字段

八、一句话总结

entrySet() = 一次拿到 key + value(高效遍历)
keySet() = 只拿 key(轻量但功能有限)

✅ "需要键和值,就用 entrySet;只要键,用 keySet。"

------ 这就是高质量 Kotlin JSON 代码的底层逻辑。

相关推荐
传奇开心果编程3 小时前
【Jetpack Compose娓娓道来】第29课:状态容器与UI事件建模——把“散装状态”变成“可穷举的契约”
android·kotlin·移动开发·android jetpack·ui框架·开发范式·声明式ui
五彩小白4 小时前
喵呜打卡项目总结
android·团队开发
Martina_03214 小时前
AI生成的3D模型打印后沿层纹开裂?用6步检查方向、壁厚与填充
图像处理·人工智能·数学建模·3d·自然语言处理·游戏策划·1024程序员节
c_hn_0075 小时前
量化数据12:国内期货L2历史数据:Tick、逐笔、五档盘口一次性打包下载
数据库·1024程序员节·#深度订单簿数据·#历史行情订单薄·#本地csv数据存储
古法安卓6 小时前
Android-GPU压测工具
android·java·android studio
释厄6236 小时前
同性线套娃律——圆面积必然=1+2+...+n 的求和·圆心不与 π 相关
java·人工智能·算法·1024程序员节
xcLeigh7 小时前
KingbaseES数据库教程:初识数据库,从安装到跑通第一条SQL
android·数据库·sql·安装·数据库教程·kes
在所不辞兄8 小时前
【零基础学智能仿真-69】综合项目——材料参数驱动的仿真研究
人工智能·深度学习·神经网络·算法·机器学习·1024程序员节
桑年9 小时前
四、Pandas数据处理
人工智能·python·线性代数·pandas·数据可视化·数据处理
Captain1314_李祖团10 小时前
章节3:MLIR完成一个端到端的流程
mlir·1024程序员节