【问题标题】:How to implement mutable optional in Kotlin?如何在 Kotlin 中实现可变可选?
【发布时间】:2018-10-07 01:56:54
【问题描述】:

我想要一个与 Java Optional 等价的类

  • 正确处理空值(“未设置”状态与“空设置”不同)
  • 是可变的
  • 使用 Kotlin 内置的 null 安全性,类型参数可以为 null 或不可为 null,这会影响所有方法。

非工作代码:

class MutableOptional<T> {
    private var value: T? = null
    private var isSet: Boolean = false

    fun set(value: T)
    {
        this.value = value
        isSet = true
    }

    fun unset()
    {
        isSet = false
        value = null
    }

    fun get(): T
    {
        if (!isSet) {
            throw Error("Value not set")
        }
        return value!! // <<< NPE here
    }
}

fun f()
{
    val opt = MutableOptional<Int?>()
    opt.set(null)
    assertNull(opt.get())
}

问题是,如果我尝试设置 null,get() 调用会失败并出现空指针异常(由 !! 运算符引起)。

一些无效的建议:

  • 不要使用“T?”类型的成员?在这样的课程中。如果我知道如何使它们保持未初始化(编译器不允许)或如何使它们具有默认初始化,我不会使用它。
  • 使用“fun get(): T?” (结果可为空)。我希望结果类型与类类型参数具有相同的可空性。否则,如果它在一个简单的泛型类中丢失,那么这种空安全性就没有任何意义,我需要设置 !!在我确定它不可为空的地方手动手动操作(编译器应该确保的事情),使我的代码看起来像楔形书写。

注意:这个例子是合成的,我不需要 mutable 可选,它只是一个简单易懂的例子,说明了我偶尔遇到的 Kotlin 泛型和空安全问题。找到这个特定示例的解决方案将有助于解决许多类似的问题。实际上我有这个类的不可变版本的解决方案,但它涉及为当前和非当前值制作接口和两个实现类。这种不可变的可选项可以用作“值”成员的类型,但我认为这是相当大的开销(会计还为每个 set() 创建包装器对象)只是为了克服语言限制。

【问题讨论】:

  • 更好的做法:you 不应处理 null,但只有在泛型类型为 null 时才应处理 null。也就是说,value 应该具有 T 类型,但是如果用户想要允许可空的 Foos,则可以创建 MutableOptional&lt;Foo?&gt;
  • 是的,这正是问题中描述的 MutableOptional 类的要求。并且还提到了使value 类型为T 的问题-您无法将其初始化为某些东西。但是,现在我看到它可以初始化为null as T,这是实现的第二个变体,其中包含接受的答案。

标签: kotlin


【解决方案1】:

编译器希望您编写对所有可能的T 类型安全的代码,包括可空和非空(除非您为类型参数指定非空上限,例如T : Any,但这不是你需要的)。

如果您将T? 存储在属性中,则在非空类型参数的情况下,它与T 的类型不同,因此您不能互换使用TT?

但是,创建unchecked cast 允许您绕过限制并将T? 值返回为T。与非空断言 (!!) 不同,强制转换不会在运行时检查,并且在遇到 null 时不会失败。

修改get()函数如下:

fun get(): T {
    if (!isSet) {
        throw Error("Value not set")
    }
    @Suppress("unchecked_cast")
    return value as T
}

【讨论】:

  • 谢谢,我错过了这样的演员阵容。在这种特殊情况下,代码可以确保它是有效的,(如果 T 不可为空,则无法设置 null,“set(null)”将无法编译,这正是我所期望的)。
【解决方案2】:

我遇到了类似的问题。我的用例是在反序列化 JSON 对象时区分空值和未定义值。所以我创建了一个能够处理空值的不可变 Optional。在这里分享一下我的解决方案:

interface Optional<out T> {
    fun isDefined(): Boolean
    fun isUndefined(): Boolean
    fun get(): T
    fun ifDefined(consumer: (T) -> Unit)

    class Defined<out T>(private val value: T) : Optional<T> {
        override fun isDefined() = true
        override fun isUndefined() = false
        override fun get() = this.value
        override fun ifDefined(consumer: (T) -> Unit) = consumer(this.value)
    }

    object Undefined : Optional<Nothing> {
        override fun isDefined() = false
        override fun isUndefined() = true
        override fun get() = throw NoSuchElementException("No value defined")
        override fun ifDefined(consumer: (Nothing) -> Unit) {}
    }
}

fun <T> Optional<T>.orElse(other: T): T = if (this.isDefined()) this.get() else other

诀窍:必须将 orElse 方法定义为不破坏协方差的扩展,因为Kotlin does not support lower bound for now

然后我们可以通过以下方式定义一个没有强制转换的 MutableOptional:

class MutableOptional<T> {

    private var value: Optional<T> = Optional.Undefined

    fun get() = value.get()
    fun set(value: T) { this.value = Optional.Defined(value) }
    fun unset() { this.value = Optional.Undefined }
}

我对我的不可变 Optional 实现感到满意。但是我对 MutableOptional 不是很满意:我不喜欢之前基于强制转换的解决方案(我不喜欢强制转换)。但是我的解决方案会产生不必要的拳击,这可能是最糟糕的......

【讨论】:

    猜你喜欢
    • 2018-06-11
    • 1970-01-01
    • 2020-01-02
    • 2018-07-24
    • 1970-01-01
    • 2019-03-19
    • 2018-04-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多