45

假设我有一个activitiestype变量List<Any>?。如果列表不为空且不为空,我想做点什么,否则我想做点别的。我想出了以下解决方案:

when {
    activities != null && !activities.empty -> doSomething
    else -> doSomethingElse
}

在 Kotlin 中是否有更惯用的方法来做到这一点?

4

9 回答 9

50

对于一些简单的操作,您可以使用安全调用运算符,假设该操作还尊重不在空列表上操作(处理您的null和空的情况:

myList?.forEach { ...only iterates if not null and not empty }

对于其他动作。您可以编写一个扩展函数——两种变体取决于您是否希望将列表作为this参数接收:

inline fun <E: Any, T: Collection<E>> T?.withNotNullNorEmpty(func: T.() -> Unit): Unit {
    if (this != null && this.isNotEmpty()) {
        with (this) { func() }
    }
}

inline fun  <E: Any, T: Collection<E>> T?.whenNotNullNorEmpty(func: (T) -> Unit): Unit {
    if (this != null && this.isNotEmpty()) {
        func(this)
    }
}

您可以将其用作:

fun foo() {  
    val something: List<String>? = makeListOrNot()
    something.withNotNullNorEmpty { 
        // do anything I want, list is `this`
    }

    something.whenNotNullNorEmpty { myList ->
        // do anything I want, list is `myList`
    }
}

您还可以执行反函数:

inline fun <E: Any, T: Collection<E>> T?.withNullOrEmpty(func: () -> Unit): Unit {
    if (this == null || this.isEmpty()) {
        func()
    }
}

我会避免将这些链接起来,因为那样你就会用更冗长的东西替换ifor语句。when而且您正在更多地进入我在下面提到的替代方案提供的领域,这是成功/失败情况的完整分支。

Collections注意:这些扩展被推广到所有持有非空值的后代。并且不仅仅为列表工作。

备择方案:

Kotlin的Result库提供了一种很好的方法来处理基于响应值的“做这个或那个”的情况。对于 Promises,您可以在Kovenant库 中找到相同的内容。

这两个库都为您提供了从单个函数返回替代结果以及基于结果分支代码的方式。 他们确实要求您控制所采取的“答案”的提供者。

这些是 和 的良好 Kotlin 替代OptionalMaybe

进一步探索扩展功能(也许太多了)

本节只是为了表明,当您遇到类似于此处提出的问题的问题时,您可以轻松地在 Kotlin 中找到许多答案,从而按照您想要的方式进行编码。如果这个世界不讨人喜欢,那就改变这个世界。它不是一个好或坏的答案,而是附加信息。

如果您喜欢扩展函数并想考虑将它们链接到表达式中,我可能会将它们更改如下...

返回的withXyz味道thiswhenXyz应该返回的新类型允许整个集合成为一些新的(甚至可能与原始集合无关)。产生如下代码:

val BAD_PREFIX = "abc"
fun example(someList: List<String>?) {
    someList?.filterNot { it.startsWith(BAD_PREFIX) }
            ?.sorted()
            .withNotNullNorEmpty {
                // do something with `this` list and return itself automatically
            }
            .whenNotNullNorEmpty { list ->
                // do something to replace `list` with something new
                listOf("x","y","z")
            }
            .whenNullOrEmpty {
                // other code returning something new to replace the null or empty list
                setOf("was","null","but","not","now")
            }
}

注:此版本的完整代码在文末(1)

但是你也可以通过自定义的“this else that”机制走向一个全新的方向:

fun foo(someList: List<String>?) {
    someList.whenNullOrEmpty {
        // other code
    }
    .otherwise { list ->
        // do something with `list`
    }
}

没有限制,要有创意,学习扩展的力量,尝试新的想法,正如你所看到的,人们对这类情况的编码方式有很多变化。标准库不能支持这些类型的方法的 8 种变体而不会造成混淆。但是每个开发组都可以拥有与其编码风格相匹配的扩展。

注:本版本完整代码在文末(2)

示例代码 1: 这是“链式”版本的完整代码:

inline fun <E: Any, T: Collection<E>> T?.withNotNullNorEmpty(func: T.() -> Unit): T? {
    if (this != null && this.isNotEmpty()) {
        with (this) { func() }
    }
    return this
}

inline fun  <E: Any, T: Collection<E>, R: Any> T?.whenNotNullNorEmpty(func: (T) -> R?): R? {
    if (this != null && this.isNotEmpty()) {
        return func(this)
    }
    return null
}

inline fun <E: Any, T: Collection<E>> T?.withNullOrEmpty(func: () -> Unit): T? {
    if (this == null || this.isEmpty()) {
        func()
    }
    return this
}

inline fun <E: Any, T: Collection<E>, R: Any> T?.whenNullOrEmpty(func: () -> R?): R?  {
    if (this == null || this.isEmpty()) {
        return func()
    }
    return null
}

示例代码 2: 这是“this else that”库的完整代码(带有单元测试):

inline fun <E : Any, T : Collection<E>> T?.withNotNullNorEmpty(func: T.() -> Unit): Otherwise {
    return if (this != null && this.isNotEmpty()) {
        with (this) { func() }
        OtherwiseIgnore
    } else {
        OtherwiseInvoke
    }
}

inline fun  <E : Any, T : Collection<E>> T?.whenNotNullNorEmpty(func: (T) -> Unit): Otherwise {
    return if (this != null && this.isNotEmpty()) {
        func(this)
        OtherwiseIgnore
    } else {
        OtherwiseInvoke
    }
}

inline fun <E : Any, T : Collection<E>> T?.withNullOrEmpty(func: () -> Unit): OtherwiseWithValue<T> {
    return if (this == null || this.isEmpty()) {
        func()
        OtherwiseWithValueIgnore<T>()
    } else {
        OtherwiseWithValueInvoke(this)
    }
}

inline fun <E : Any, T : Collection<E>> T?.whenNullOrEmpty(func: () -> Unit): OtherwiseWhenValue<T> {
    return if (this == null || this.isEmpty()) {
        func()
        OtherwiseWhenValueIgnore<T>()
    } else {
        OtherwiseWhenValueInvoke(this)
    }
}

interface Otherwise {
    fun otherwise(func: () -> Unit): Unit
}

object OtherwiseInvoke : Otherwise {
    override fun otherwise(func: () -> Unit): Unit {
        func()
    }
}

object OtherwiseIgnore : Otherwise {
    override fun otherwise(func: () -> Unit): Unit {
    }
}

interface OtherwiseWithValue<T> {
    fun otherwise(func: T.() -> Unit): Unit
}

class OtherwiseWithValueInvoke<T>(val value: T) : OtherwiseWithValue<T> {
    override fun otherwise(func: T.() -> Unit): Unit {
        with (value) { func() }
    }
}

class OtherwiseWithValueIgnore<T> : OtherwiseWithValue<T> {
    override fun otherwise(func: T.() -> Unit): Unit {
    }
}

interface OtherwiseWhenValue<T> {
    fun otherwise(func: (T) -> Unit): Unit
}

class OtherwiseWhenValueInvoke<T>(val value: T) : OtherwiseWhenValue<T> {
    override fun otherwise(func: (T) -> Unit): Unit {
        func(value)
    }
}

class OtherwiseWhenValueIgnore<T> : OtherwiseWhenValue<T> {
    override fun otherwise(func: (T) -> Unit): Unit {
    }
}


class TestBrancher {
    @Test fun testOne() {
        // when NOT null or empty

        emptyList<String>().whenNotNullNorEmpty { list ->
            fail("should not branch here")
        }.otherwise {
            // sucess
        }

        nullList<String>().whenNotNullNorEmpty { list ->
            fail("should not branch here")
        }.otherwise {
            // sucess
        }

        listOf("a", "b").whenNotNullNorEmpty { list ->
            assertEquals(listOf("a", "b"), list)
        }.otherwise {
            fail("should not branch here")
        }

        // when YES null or empty

        emptyList<String>().whenNullOrEmpty {
            // sucess
        }.otherwise { list ->
            fail("should not branch here")
        }

        nullList<String>().whenNullOrEmpty {
            // success
        }.otherwise {
            fail("should not branch here")
        }

        listOf("a", "b").whenNullOrEmpty {
            fail("should not branch here")
        }.otherwise { list ->
            assertEquals(listOf("a", "b"), list)
        }

        // with NOT null or empty

        emptyList<String>().withNotNullNorEmpty {
            fail("should not branch here")
        }.otherwise {
            // sucess
        }

        nullList<String>().withNotNullNorEmpty {
            fail("should not branch here")
        }.otherwise {
            // sucess
        }

        listOf("a", "b").withNotNullNorEmpty {
            assertEquals(listOf("a", "b"), this)
        }.otherwise {
            fail("should not branch here")
        }

        // with YES null or empty

        emptyList<String>().withNullOrEmpty {
            // sucess
        }.otherwise {
            fail("should not branch here")
        }

        nullList<String>().withNullOrEmpty {
            // success
        }.otherwise {
            fail("should not branch here")
        }

        listOf("a", "b").withNullOrEmpty {
            fail("should not branch here")
        }.otherwise {
            assertEquals(listOf("a", "b"), this)
        }


    }

    fun <T : Any> nullList(): List<T>? = null
}
于 2015-12-28T18:37:26.657 回答
44

更新:

kotlin 1.3isNullOrEmpty现在提供!

https://twitter.com/kotlin/status/1050426794682306562


尝试这个!非常清楚。

var array: List<String>? = null
if (array.orEmpty().isEmpty()) {
    // empty
} else {
    // not empty
}
于 2018-01-02T06:26:01.233 回答
6

更简单的方法是,

if(activities?.isNotEmpty() == true) doSomething() else doSomethingElse()
于 2018-06-18T10:31:38.090 回答
5

除了其他答案之外,您还可以将安全调用运算符与扩展方法结合使用isNotEmpty()。由于安全调用,返回值实际上Boolean?可以是true,falsenull. if要在or子句中使用表达式when,您需要明确检查它是否为true

when {
    activities?.isNotEmpty() == true -> doSomething
    else -> doSomethingElse
}

使用 elvis 运算符的替代语法:

when {
    activities?.isNotEmpty() ?: false -> doSomething
    else -> doSomethingElse
}
于 2016-05-16T09:26:26.043 回答
3

?.forEach酌情考虑使用

activities?.forEach {
  doSmth(it)
}

如果你想要你所描述的行为,我认为你的变体比我能想到的更简洁的任何东西都更好。(但简单if就足够了)

于 2014-10-15T13:43:32.990 回答
2

在 Kotlin 1.3 中使用的实际方法isNullOrEmpty就像这个答案中提到的那样:https ://stackoverflow.com/a/48056456/2735286

以下是它的用法示例:

fun main(args: Array<String>) {
    var array: MutableList<String>? = null
    println(array.isNullOrEmpty()) // true
    array = mutableListOf()
    println(array.isNullOrEmpty()) // true
    array = mutableListOf("a")
    println(array.isNullOrEmpty()) // false
}

这个例子打印出来:

true
true
false
于 2019-04-29T16:02:58.250 回答
1

Kotlin 1.3 有扩展 isNullOrEmpty。简短的回答是:

if (activities.isNullOrEmpty) doSomething
else doSomethingElse

扩展定义为:

fun <T> Collection<T>?.isNullOrEmpty(): Boolean

字符串和数组也存在类似的扩展。

于 2019-08-24T21:35:02.113 回答
0

在我的情况下,价格是可选的。我以以下方式处理这种情况,orEmpty()如果给定数组为空,则返回给定数组或空数组。

val safeArray  = poi.prices.orEmpty()
if (!safeArray.isEmpty()) {
   ...
}
于 2018-11-22T11:55:52.110 回答
-1

首先,我想建议在处理else条件的@mltu's answer之外制作扩展功能

 
public inline fun  Map.forEachElse(operation: (Map.Entry) -> Unit, elseBlock: () -> Unit): Unit {
        if (!empty)
            for (element in this) operation(element)
        else
            elseBlock()
    }

但是用法没有那么漂亮。

实际上你正在寻找一个 Maybe monad

于 2014-10-15T14:23:20.057 回答