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 代码的底层逻辑。

相关推荐
hai_android4 小时前
Android 嵌套滑动冲突:内部拦截 vs 外部拦截实战
android·android studio·android jetpack
木子欢儿6 小时前
Zabbix 7.4 Alpine 版 Docker Compose 部署
android·运维·docker·容器·zabbix
素师良码7 小时前
第9篇:Android驱动踩坑实录:DSI RX FIFO前置脏0、数据错位复盘
android
夜雪一千7 小时前
如何在MySQL中实现字段级加密
android·数据库·mysql
夜雪一千7 小时前
MySQL 如何存储经纬度坐标:选型、建表、空间查询实战
android·mysql·adb
JMchen7 小时前
集成第三方 C/C++ 库到 Android NDK 项目:OpenCV 与 FFmpeg 实战指南
android·opencv·cmake
有毒的教程9 小时前
Dev-C++ 使用扩展库两种方式:DevPak一键安装 / 手动配置(重点)
android·c++·adb
mmsx9 小时前
Android 测绘数值基础:测量单位系统与七四参数、高程拟合
android
郑州光合科技余经理9 小时前
出海外卖平台:支付对接为什么要和下单收款分开
android·java·开发语言·前端·后端·php·ai编程
夜雪一千9 小时前
MySQL 如何提高查询速度:从索引、SQL、表结构到实战调优
android·sql·mysql