如果没有显式的空检查,编译器将无法识别非空值

时间:2019-05-09 12:00:09

标签: android-studio intellij-idea kotlin null

示例:

class Object(val value: Int?) {
   fun doesNotContainNull() = value != null
} 

Object obj = Object(randomValue)

然后,当我使用此对象时,可以像这样进行空检查:


if (obj.value != null) {
  passNonNullableValue(obj.value) // compiles
}

或者这样

if (obj.doesNotContainNull()) {
  passNonNullableValue(obj.value) // does not compile
}

为什么编译器会抱怨第二个窥探者?无法解析该值不能为null吗?还是我错过了什么?

2 个答案:

答案 0 :(得分:3)

TL; DR:签出this playground

通过Kotlin合同,这是 sort 的一种可能-Kotlin 1.3的实验功能,可增强对智能演员的支持。

这是contract的一个简单示例:

@ExperimentalContracts
fun Object?.isNotNull(): Boolean {

    contract {
        // If I return "true", then it means that I am not null
        returns(true) implies (this@isNotNull != null)
    }

    return this != null
}

if (obj.isNotNull())
    print(obj.value) // smart-cast applied.

现在我在这里说 sort 是因为合同的使用有一些限制。仅顶级功能支持它们。这意味着您将必须创建Object类的扩展。 另一个限制是我们要在此处检查Object.value的可为空性,而不是Object本身。合同定义中不支持此功能。因此,即使看起来很理想,也不会编译以下内容:

@ExperimentalContracts
fun Object.doesNotContainNull(): Boolean {

    contract {

        // The following line gives an error:
        // Error in contract description: only references to parameters are allowed in contract description.
        returns(true) implies (this@doesNotContainNull.value != null)    }

    return value != null
}

正如错误所暗示的,我们可以在合同说明中引用参数。因此,我们可以提出一种解决方法,在该方法中我们传递value作为参数。最后,该函数将如下所示:

@ExperimentalContracts
fun Object.doesNotContainNull(value: Int?): Boolean {
    contract {
        returns(true) implies (value != null)
    }

    return value != null
}

if (obj.doesNotContainNull(obj.value)) {
    passNonNullableValue(obj.value) // smart cast applied!
}

即使这种方法可行,也很危险,因为调用者可能并不总是通过obj.value作为参数。因此,我们希望确保该条件并在检查失败时抛出运行时错误。

@ExperimentalContracts
fun Object.doesNotContainNull(value: Int?): Boolean {
    contract {

        // "If I return true, then it means that $value is not null."
        returns(true) implies (value != null)
    }

    if(value != this.value)
        throw IllegalArgumentException("$value must be ${this.value}")

    return value != null
}

可以在this playground中播放完整的示例。

答案 1 :(得分:0)

Kotlin 编译器不知道调用doesNotContainNull()的含义。为了提供此信息,引入了kotlin.contracts。尽管此API仍有一些缺点,但是您可以使用sealed class方法将其抽象化。

sealed class Object {
    abstract val value: Int?
    class NotNull(override val value: Int) : Object()
    object Null : Object() {
        override val value: Int? = null
    }

    companion object {
        operator fun invoke(value: Int?) = if (value == null)
            Object.Null else Object.NotNull(value)
    }
} 

fun Object.doesNotContainNull(): Boolean {
    contract {
        returns(true) implies (this@doesNotContainNull is Object.NotNull)
    }
    return value != null
}

可以像执行所有操作一样使用。虽然有点冗长。

但是最终使用?.let可能更容易阅读和理解。

obj.value?.let {
    passNonNullableValue(it)
}