理解 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() 更干净。
六、实践建议
-
固定习惯 :遍历 Map/Json 时默认用
entrySet()。 -
明确策略:合并时清楚覆盖逻辑(覆盖/补齐/深度合并)。
-
必要时深拷贝:避免 JsonElement 共享引用。
-
日志友好:当字段被覆盖时可打印提示,便于调试。
七、总结记忆表
| 目的 | 推荐写法 | 说明 |
|---|---|---|
| 遍历所有字段 | 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 代码的底层逻辑。