Kotlin 和惯用的写法,‘如果不为空,则为...’基于可变值

假设我们有这样的代码:

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 不同,在 Java 中,您可以独自担心运行时的 null 解引用问题,而这种方法不能编译——这是正确的。当然,mutableProperty在“ 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 风格的处理方式更加笨拙和冗长。

我是否遗漏了什么,有没有一个更好的习语来实现这一点?

78321 次浏览

I don't believe there is a really "short" way to achieve it, however you can simply use a conditional within with or let:

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

In fact, "capturing" a mutable value is one of the main use cases of let.

This is equivalent to your when statement.

There is always the option you described, assigning it to a variable:

val immutable = mutableVar


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

which is always a nice fallback in case e.g. things get too verbose.

There probably isn't really a very nice way to achieve this because only the last lambda argument is allowed to be put outside the (), so specifying two wouldn't really fit the syntax of all of the other standard functions.

You could write one if you don't mind that (or if you'll be passing method references instead):

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") })

Note that I would personally not do this, because none of these custom constructs are very pleasant to read. IMO: stick with let/run and fall back to if-else when necessary.

Update:

As mentioned by franta on the comments, if the method doSomething() returns null, then the code on the right side of the elvis operator will be executed, which might not be the desired case for most. But at the same time, in this case, it is very likely that the doSomething() method will only do something and not return anything.

And an alternative: as protossor has mentioned on the comments, also can be used rather than let, because also returns this object instead of the result of the function block.

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

Original answer:

I would use let with Elvis operator.

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

From the doc:

If the expression to the left of ?: is not null, the elvis operator returns it, otherwise it returns the expression to the right. Note that the right-hand side expression is evaluated only if the left-hand side is null.

For a block of code after the right-hand side expression:

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

add custom inline function as below:

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


inline fun <T> T?.whenNonNull(block: T.() -> Unit): T? {
this?.block()
return this@whenNonNull
}

then you can write code like this:

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

i usually write it like this:

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

note the question mark after takeIf is a MUST. you can use also or apply keyword.

You could also do something like this:

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

You can then use it like this:

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


}

关于什么:

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

I usually just do:

when(val it=argument.mutableProperty) {
null -> doOtherThing()
else -> doSomething(it)
}

Thanks to @zyc zyc , now I use this code

inline fun <T> T?.ifNull(block: () -> Unit): T? {
if (this == null) block()
return this@ifNull
}


inline fun <T> T?.ifNonNull(block: (T) -> Unit): T? {
this?.let(block)
return this@ifNonNull
}
// use
xxxx.ifNull {
// todo
}.ifNonNull {
// todo
}