Есть ли изящный kotlin способ убедить компилятор в том, что пустое поле, которому я только что присвоил реальное значение, больше не может быть нулевым? - PullRequest
0 голосов
/ 26 января 2019

Я прочитал это, используя !! как правило, следует избегать. Есть ли способ написать следующий код более элегантным способом без необходимости добавлять что-то вроде устаревших нулевых проверок и дублированных или мертвых блоков кода?

class A(var field: Thing?) {
    fun getField(): Thing {
        if (field == null) {
            field = Thing()
        }
        return field!!
    }
}

Также я не понимаю, почему компилятор требует, чтобы в этом сценарии выполнялся оператор !! - «молись-не-ноль-оператор».

РЕДАКТИРОВАТЬ: Учтите, что для меня важно, чтобы потенциальное решение использовало ленивую инициализацию, если поле пустое!

Ответы [ 3 ]

0 голосов
/ 26 января 2019

Проблема

Как уже упоминал Энзоки в комментариях, другой поток мог изменить поле после проверки на ноль.Компилятор не может этого знать, поэтому вы должны сказать это.

class A(var field: Thing?) {

    fun getField(): Thing {
        if (field == null) {
            field = Thing()
        }

        // another thread could have assigned null to field

        return field!! // tell the compiler: I am sure that did not happen
    }
}

Solution (Eager)

В вашем конкретном случае это было бы хорошоИдея использовать параметр f (вы могли бы также назвать его «полем», но я избегал этого для ясности) в конструкторе (без val / var) и впоследствии назначить его свойству field, которомуВы назначаете либо f, либо новый экземпляр Thing.

Это можно выразить очень кратко с помощью оператора Элвиса :?, который принимает левую сторону, если не ноль, и правую часть выражения в противном случае.Итак, в конце поле будет иметь тип Thing.

class A(f: Thing?) {
    val field = f ?: Thing() // inferred type Thing
}

Solution (Lazy)

Так как оно было упомянуто gidds , если вам нужно лениво инициализировать поле, вы можете сделать это, используя делегированные свойства :

class A(f: Thing?) {
    val field by lazy {
        f ?: Thing() // inferred type Thing
    }
}

Сайт вызова не изменяется:

val a = A(null) // field won't be initialized after this line...
a.field // ... but after this
0 голосов
/ 26 января 2019

Когда вы определяете поле, вы фактически определяете переменную плюс два метода доступа :

val counter: Integer = 0

Можно настроить методы доступа, написав вместо этого:

val n = 0
val counter: Integer
    get() = n++

Это будет выполнять n++ каждый раз, когда вы обращаетесь к полю counter, которое поэтому возвращает разные значения при каждом доступе.Это необычно и неожиданно, но технически возможно.

Поэтому компилятор Kotlin не может предположить, что два доступа к одному и тому же полю возвращают одно и то же значение дважды.Обычно они это делают, но это не гарантируется.

Чтобы обойти это, вы можете прочитать поле один раз, скопировав его в локальную переменную:

fun count() {
    val counter = counter
    println("The counter is $counter, and it is still $counter.")
}
0 голосов
/ 26 января 2019

Как насчет этого?

class A(field: Thing?) {

    private lateinit var field: Thing

    init {
        field?.let { this.field = it }
    }

    fun getField(): Thing {
        if (!this::field.isInitialized) {
            field = Thing()
        }
        return field
    }
}
...