Kotlin 和惯用的编写方式,'if not null, else...' 基于可变值

Kotlin and idiomatic way to write, 'if not null, else...' based around mutable value

假设我们有这样的代码:

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

这有一个明显的缺点,即您需要尽早 return 或避免执行后续代码 - 在某些小的上下文中可以,但有异味。

那么有这种可能:

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

虽然它的目的更加清晰,但可以说它比 Java 风格的处理方式更笨拙和冗长。

我是否遗漏了什么,是否有一个首选的成语可以实现这一目标?

我认为没有真正的 "short" 方法可以实现它,但是您可以简单地在 withlet:

中使用条件语句
with(mutableVar) { if (this != null) doSomething(this) else doOtherThing() }
mutableVar.let { if (it != null) doSomething(it) else doOtherThing() }

事实上,"capturing" 可变值是 let 的主要用例之一。

这等同于您的 when 声明。

总有你描述的那个选项,赋值给一个变量:

val immutable = mutableVar

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

这始终是一个不错的后备方案,以防万一事情变得太冗长了。

可能真的没有非常nice的方法来实现这个,因为只允许将 last lambda 参数放在外面(),因此指定两个并不真正适合所有其他标准函数的语法。

如果您不介意(或者如果您将传递方法引用),您可以写一个:

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

请注意,我个人 不会 这样做,因为 none 这些自定义结构读起来非常愉快。 IMO:坚持 let/run 并在必要时退回到 if-else

更新:

正如franta在评论中提到的,如果方法doSomething() return为null,那么将执行elvis运算符右侧的代码,这可能不是我们想要的大多数情况下。但与此同时,在这种情况下,很可能 doSomething() 方法只会做一些事情而不会 return 任何事情。

还有一个替代方案:正如 protossor 在评论中提到的,可以使用 also 而不是 let,因为 also returns this对象而不是功能块的结果。

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

原回答:

我会使用 letElvis operator

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

来自文档:

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.

对于右侧表达式之后的代码块:

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

添加自定义内联函数如下:

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
}

那么你可以这样写代码:

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

我一般是这样写的:

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

注意 takeIf 后面的问号是必须的。您可以使用 also 或 apply 关键字。

你也可以这样做:

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 {

}

怎么样:

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

我通常这样做:

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

感谢@zyc zyc,现在我使用这个代码

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
}