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 {
}