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 的三种遍历方式

GsonJsonObject 本质上是一个 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 里已经包含 keyvalue,不需要再调用 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 代码的底层逻辑。

相关推荐
360智汇云5 小时前
从0到1理解智能体模式
1024程序员节
旷野说5 小时前
Spring Boot 1.x、2.x 3.x区别汇总
java·spring·tomcat·1024程序员节
机器学习算法与Python实战5 小时前
一个强大的开源OCR工具,基于DeepSeek OCR
1024程序员节
blammmp5 小时前
Spring Boot集合RabbitMQ
1024程序员节
没有bug.的程序员5 小时前
Spring Boot 起步:自动装配的魔法
java·开发语言·spring boot·后端·spring·1024程序员节
Hero | 柒5 小时前
设计模式之建造者模式
java·设计模式·1024程序员节
不脱发的程序猿5 小时前
如何检测和解决I2C通信死锁
stm32·单片机·嵌入式·1024程序员节
wbs_scy5 小时前
C++类和对象(中):const 成员函数与取地址运算符重载
1024程序员节
CodeLongBear5 小时前
帝可得智能售货机系统实战Day1:从环境搭建到区域管理功能落地 (1)
java·1024程序员节·ai + 若依框架