1. 概述
在 Kotlin 中,我们可以通过 map[key]
的方式访问 Map 中的值。但该方式返回的是一个可空类型(nullable),这在我们确定值一定非空的情况下会带来不便。
本文将讨论 Kotlin 中 Map 的 get
运算符行为,并探索如何从 Map 中获取非空值。
2. 问题背景
我们先来看一个 Map 的例子:
val inputMap = mapOf(
1 to "Macbook pro, MacOS",
2 to "Dell Laptop, Windows10",
3 to "HP Laptop, Archlinux",
4 to "Thinkpad Laptop, Ubuntu Linux",
)
这是一个 Map<Int, String>
,存储了一些笔记本信息。我们尝试通过 key 获取值并进行判断:
val result = inputMap[3]
assertTrue { result!!.startsWith("HP Laptop") }
这里我们使用了 !!
操作符强制解包,因为 map[key]
返回的是一个可空类型 V?
。这是 Kotlin 中 Map 的标准方法:
public operator fun get(key: K): V?
如果我们尝试访问一个不存在的 key,比如:
val nullResult = inputMap[42]
assertNull(nullResult)
它会返回 null
。Kotlin 的空安全机制虽然强大,但在我们明确知道 key 一定存在时,频繁使用 !!
会显得啰嗦。
那么,有没有办法让我们在访问 Map 时直接返回非空值呢?我们来看几个解决方案。
3. 使用 getValue() 方法
Kotlin 的 Map 提供了一个 getValue()
方法,用于获取非空值。如果 key 不存在,它会抛出 NoSuchElementException
。
我们来看一个测试示例:
val result = inputMap.getValue(3)
assertTrue { result.startsWith("HP Laptop") }
assertThrows<NoSuchElementException> { inputMap.getValue(42) }
这次我们无需使用 !!
,因为 getValue()
返回的是非空类型。这在某些场景下非常实用。
但问题在于,getValue()
并不是 map[key]
风格的语法,我们是否可以让 map[key]
也返回非空值呢?
答案是肯定的,我们可以通过重载 get
运算符来实现。
4. 使用匿名对象封装 Map
我们可以通过创建一个匿名对象,并重载其 get
运算符,让其返回非空值:
val laptops = object {
val theMap = inputMap
operator fun get(key: Int): String = theMap[key]!!
}
然后进行测试:
val result = laptops[3]
assertTrue { result.startsWith("HP Laptop") }
assertThrows<NullPointerException> { laptops[42] }
✅ 优点:我们实现了 map[key]
返回非空值
❌ 缺点:此时 laptops
的类型是 Any
,不再是 Map
,无法直接使用 Map 的方法,如 laptops.size
会编译失败
因此,我们需要进一步优化。
5. 创建 NotNullMap 类并委托给 Map
我们可以通过 Kotlin 的委托模式创建一个 NotNullMap
类,让它实现 Map
接口并重载 get
运算符:
class NotNullMap<K, V>(private val map: Map<K, V>) : Map<K, V> by map {
override operator fun get(key: K): V {
return map[key]!! // 如果 key 不存在,抛出 NullPointerException
}
}
使用方式如下:
val laptops = NotNullMap(inputMap)
assertTrue { laptops[3].startsWith("HP Laptop") }
assertThrows<NullPointerException> { laptops[42] }
✅ 优点:
- 保留了
Map
的所有方法 map[key]
返回非空值- 可以直接使用
size
、keys
等 Map 属性
为了更方便地初始化 NotNullMap
,我们可以提供一个工厂函数:
fun <K, V> notNullMapOf(vararg pairs: Pair<K, V>) = NotNullMap(mapOf(*pairs))
使用方式:
val personHobbies = notNullMapOf(
"Tom" to setOf("Reading", "hiking"),
"Jackson" to setOf("Reading", "Football"),
"Anna" to setOf("Singing", "Tennis")
)
assertEquals(2, personHobbies["Tom"].size)
assertThrows<NullPointerException> { personHobbies["Shirley"] }
这个方案已经非常接近我们理想的状态了。
6. 小结
本文我们探讨了如何从 Map 中获取非空值的几种方式:
方法 | 是否返回非空 | 是否保留 Map 接口 | 是否支持 map[key] |
抛出异常类型 |
---|---|---|---|---|
map[key] |
❌ | ✅ | ✅ | 返回 null |
getValue() |
✅ | ✅ | ❌ | NoSuchElementException |
匿名对象封装 | ✅ | ❌ | ✅ | NullPointerException |
NotNullMap + 委托 |
✅ | ✅ | ✅ | NullPointerException |
最终推荐使用 NotNullMap
+ 委托 + 工厂函数 的方式,既保留了 Map 的功能,又支持了非空访问的语法。
完整示例代码已上传至 GitHub。