Компилятор не распознает ненулевое значение, если нет явной проверки нуля - PullRequest
1 голос
/ 09 мая 2019

Пример:

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
}

Почему компилятор жалуется на второй сниппер? Разве это не может решить, что значение не может быть нулевым? Или я что-то упустил?

Ответы [ 2 ]

3 голосов
/ 09 мая 2019

TL; DR: Проверьте эту игровую площадку .

Это сорт , возможный с помощью контрактов Kotlin - экспериментальная функция Kotlin 1.3 для улучшения поддержки Smart Cast.

Вот один простой пример 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.

Теперь я говорю вроде , потому что использование контракта имеет некоторые ограничения. Их поддерживают только функции верхнего уровня. Это означает, что вам нужно создать расширение класса 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
}

Полный пример можно сыграть на этой игровой площадке .

0 голосов
/ 09 мая 2019

Компилятор 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)
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...