Kotlin 为 Map 提供的默认值相关的扩展

本文转载自:https://mp.weixin.qq.com/s/iovPlEV9ZwEEv2Xj2KLIPw

Map 的 Value 类型是一个可空类型,Kotlin 早就想好了怎么帮你优雅地面对它。

Map 是我们经常用到的集合框架的一种,Java 标准库当中提供的 Map 的实现也是比较好用的。不过 KotlinMap 提供了几处默认值相关的扩展,让 Map 的使用变得更加轻松,不知道大家有没有注意到呢?

1. getOrElse

这个比较简单,我们先来看下它的定义:

inline fun <K, V> Map<K, V>.getOrElse(
  key: K, 
  defaultValue: () -> V
): V = get(key) ?: defaultValue()

如果没有元素 key,那么就返回默认值,默认值通过对参数中的 defaultValue 进行求值得到。当然,如果不需要默认值,那么这个求值过程也是不会发生的。

这种情况比较适合空值的语义与默认值相同的情况,例如用 Map 中的值做为某种配置:

val config = HashMap<String, Boolean>()
val isEnabled = config.getOrElse("isEnabled", { false })

2. getOrPut

需要注意的是,getOrElse() 的调用过程中 Map 没有被修改,即默认值并没有真正成为 Map 的元素。如果我们有下面的需求,那就要考虑使用 getOrPut() 了:

我们有一个事件回调接口,不同事件用 eventId 来区分:

interface OnEventListener {
    fun onEvent(eventId: String, data: String)
}

我们需要提供对事件回调注册的能力:

val eventListeners = HashMap<String, ArrayList<OnEventListener>>()

fun addOnEventListener(eventId: String, listener: OnEventListener) {
    ...
}

添加 listener 的思路也很简单,先看看 eventListeners 当中有没有对应的 eventId 的事件回调 list,如果有,直接添加;如果没有,先构造一个 list 实例,然后再添加。所以最为朴素的实现就是下面这样:

fun addOnEventListener(eventId: String, listener: OnEventListener) {
    var listenerList = eventListeners[eventId]
    if (listenerList == null) {
        listenerList = ArrayList()
        eventListeners[eventId] = listenerList
    }
    listenerList.add(listener)
}

但这个也太不 Kotlin 了。

好在我们有 getOrPut(),它提供了在 Map 中不存在对应的 Key 时返回默认值并将默认值添加到 Map 中的能力,它的实现非常直接:

inline fun <K, V> MutableMap<K, V>.getOrPut(
  key: K, defaultValue: () -> V
): V {
    val value = get(key)
    return if (value == null) {
        val answer = defaultValue()
        put(key, answer)
        answer
    } else {
        value
    }
}

既然如此,我们就可以简化 addOnEventListener 的实现了:

fun addOnEventListener(eventId: String, listener: OnEventListener) {
    eventListeners.getOrPut(eventId, defaultCreator).add(listener)
}

3. 隐式默认值

除了在获取时才能确定的默认值以外,还有一个在 Map 定义的时候就可以指定的方式,即:

val config = HashMap<String, Boolean>().withDefault { false }

这个也被称为隐式默认值,它的效果与 getOrElse() 一致,在获取某一个不存在的 Key 时,直接返回默认值表达式的求值结果,在上面的例子当中就是 { false } 的求值结果了。

不过如果大家用过这个功能的话,应该一开始都会感到比较疑惑,例如:

val isEnabled = config["isEnabled"] // null

用于此时我们的 config 只是一个空 Map,因此不存在 isEnabled 这个 Key,按照我们的直觉,这时应该触发默认值的求值过程并返回 false 对吧?但实际上不是这样的,标准库 API 的设计者为了确保对应的实现复合 Map 的接口定义,在我们调用 Map 接口的方法时,行为与普通的 Map 保持一致,因此 config[“isEnabled”] 或者说等价的 config.get(“isEnabled”) 调用并不会触发默认值的求值。

那我想要获得默认值要怎么做呢?

调用另外的一个扩展方法:

val isEnabled = config.getValue("isEnabled") // false

4. 默认值的提供方式

前面提到的三种方式中,默认值都是通过一个函数提供的,这样做有什么好处呢?

其实如果 MapValue 类型是不可变的数据类型,那么直接使用一个默认值即可,例如 BooleanString 这样的基本类型。但对于可变的类型,例如前面例子中的 ArrayList,提供统一的默认值显然是行不通的,对象被不同的 Key 共享必然会造成逻辑的混乱。

还有一个原因,函数可以共享,只需要创建一个统一的对象,每次使用的时候复用即可,默认值本身并不总是需要,自然也并不总是需要创建出来,因此这里采用延迟计算还可以减少对象创建的成本。

5. 小结

好啦,这一篇文章没有什么有难度的地方,源码大家一看就明白,使用时只要注意其中的细节即可。


评论
 上一篇
深入理解 Gradle 框架之一:Plugin、Extension、buildSrc 深入理解 Gradle 框架之一:Plugin、Extension、buildSrc
本文转载自:https://mp.weixin.qq.com/s/mDCTtQZb6mhWOFAvLYKBSg 缘起从2018年下半年开始,因为工作需要,开始深入了解 Android Gradle plugin 和 Gradle 框架,在看
2019-06-21
下一篇 
Gradle 入门详解 Gradle 入门详解
Java 作为一门世界级主流编程语言,有一款高效易用的项目管理工具是 Java 开发者想要的。先是 2000 年的 Ant,后有 2004 年 Maven 的诞生,都在 Java 市场上取得了巨大的成功。但二者都有一定的不足和局限性。 20
2019-06-10
  目录