Kotlin и идиоматический способ писать, "если не null, else...", основанный на изменяемой ценности

Предположим, что у нас есть этот код:

class QuickExample {

    fun function(argument: SomeOtherClass) {
        if (argument.mutableProperty != null ) {
            doSomething(argument.mutableProperty)
        } else {
            doOtherThing()
        }
    }

    fun doSomething(argument: Object) {}

    fun doOtherThing() {}
}

class SomeOtherClass {
    var mutableProperty: Object? = null
}

В отличие от Java, где вас можно оставить в покое, чтобы беспокоиться о нулевой разыменовании во время выполнения, это не компилируется - совершенно правильно. Конечно, mutableProperty больше не может иметь значение null в "if".

Мой вопрос - лучший способ справиться с этим?

Несколько вариантов очевидны. Без использования каких-либо новых функций языка Kotlin наиболее простым способом является, очевидно, копирование значения в область метода, которая не будет впоследствии изменяться.

Вот так:

fun function(argument: SomeOtherClass) {
    argument.mutableProperty?.let {
        doSomething(it)
        return
    }
    doOtherThing()
}

У этого есть очевидный недостаток, что вам нужно вернуться раньше или иначе избежать выполнения последующего кода - ОК в определенных, небольших контекстах, но имеет запах к нему.

Тогда есть такая возможность:

fun function(argument: SomeOtherClass) {
    argument.mutableProperty.let {
        when {
            it != null -> {
                doSomething(it)
            }
            else -> {
                doOtherThing()
            }
        }
    }
}

но в то же время он имеет большую ясность цели, возможно, он более громоздкий и многословный, чем способ Java-стиля с этим.

Я что-то упускаю, и есть ли предпочтительная идиома для этого?

Ответы

Ответ 1

Я не верю, что есть на самом деле "короткий" путь для достижения этой цели, однако, вы можете просто использовать условный внутри with или let:

with(mutableVar) { if (this != null) doSomething(this) else doOtherThing() }
mutableVar.let { if (it != null) doSomething(it) else doOtherThing() }

Фактически, "захват" изменчивого значения является одним из основных вариантов использования let.

Это эквивалентно вашему заявлению when.

Всегда есть опция, которую вы описали, присваивая ее переменной:

val immutable = mutableVar

if (immutable != null) {
    doSomething(immutable)
} else {
    doOtherThing()
}

который всегда является хорошим резервом в случае, например, вещи становятся слишком многословными.

Вероятно, это не очень хороший способ добиться этого, потому что только последний аргумент лямбда разрешен для использования вне (), поэтому указание двух не будет соответствовать синтаксису всех других стандартных функций.

Вы можете написать один, если вы не возражаете против этого (или если вместо этого вы передадите ссылки на методы):

inline fun <T : Any, R> T?.ifNotNullOrElse(ifNotNullPath: (T) -> R, elsePath: () -> R)
        = let { if(it == null) elsePath() else ifNotNullPath(it) }

...

val a: Int? = null
a.ifNotNullOrElse({ println("not null") }, { println("null") })

Ответ 2

Обновить:

Как упоминалось в комментариях franta, если метод doSomething() возвращает значение null, то будет выполняться код с правой стороны оператора elvis, что может быть нежелательным для большинства случаев. Но в то же время в этом случае весьма вероятно, что метод doSomething() будет только что-то делать и ничего не возвращать.

И альтернатива: как упомянул протоссор в комментариях, also можно использовать вместо let, потому что also возвращает this объект вместо результата функционального блока.

mutableProperty?.also { doSomething(it) } ?: doOtherThing()

Оригинальный ответ:

Я хотел бы использовать let с оператором Элвиса.

mutableProperty?.let { doSomething(it) } ?: doOtherThing()

Из документа:

Если выражение слева от?: Не равно нулю, оператор elvis возвращает его, в противном случае возвращает выражение справа. Обратите внимание, что выражение в правой части вычисляется только в том случае, если значение в левой части равно нулю.

Для блока кода после выражения правой части:

   mutableProperty?.let {
            doSomething(it)
        } ?: run {
            doOtherThing()
            doOtherThing()
        }

Ответ 3

добавьте пользовательскую встроенную функцию, как показано ниже:

inline fun <T> T?.whenNull(block: T?.() -> Unit): T? {
    if (this == null) block()
    return [email protected]
}

inline fun <T> T?.whenNonNull(block: T.() -> Unit): T? {
    this?.block()
    return [email protected]
}

то вы можете написать код следующим образом:

var nullableVariable :Any? = null
nullableVariable.whenNonNull {
    doSomething(nullableVariable)
}.whenNull {
    doOtherThing()
}

Ответ 4

Я обычно пишу это так:

  takeIf{somecondition}?.also{put somecondition is met code}?:run{put your else code here}

обратите внимание на вопросительный знак после takeIf является ДОЛЖНЫ. вы также можете использовать или применить ключевое слово.

Ответ 5

Вы также можете сделать что-то вроде этого:

class If<T>(val any: T?, private val i: (T) -> Unit) {
    infix fun Else(e: () -> Unit) {
        if (any == null) e()
        else i(any)
    }
}

Затем вы можете использовать его так:

If(nullableString) {
   //Use string
} Else {

}