在Kotlin中处理可空或空列表的惯用方法

时间:2014-10-13 13:31:13

标签: kotlin idioms kotlin-null-safety

假设我有activities类型的变量List<Any>?。如果列表不为空且不为空,我想做某事,否则我想做其他事情。我提出了以下解决方案:

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

在Kotlin中有更多惯用的方法吗?

9 个答案:

答案 0 :(得分:28)

对于一些简单的操作,您可以使用安全调用操作符,假设操作也不考虑在空列表上操作(处理两者 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()
    }
}

我会避免将这些链接起来,因为那时你用更加罗嗦的东西替换ifwhen语句。而且您正在进一步了解我在下面提到的替代方案提供的领域,这是成功/失败情况的完全分支。

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

<强>备选方案:

Kotlin的Result库提供了一个很好的方法来处理你的情况,或者那个&#34;基于响应值。对于Promises,您可以在Kovenant库中找到相同的内容。

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

这些是OptionalMaybe的好Kotlin替代品。

探索扩展功能进一步(可能太多)

此部分只是为了表明当您遇到类似此处提出的问题的问题时,您可以在Kotlin中轻松找到许多答案,以便按照您希望的方式进行编码。如果世界不可思议,改变世界。它不是一个好的或坏的答案,而是其他信息。

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

返回withXyz thiswhenXyz的{​​{1}}风格应该返回一个新类型,允许整个集合成为一个新的集合(甚至可能与原始集合无关)。产生如下代码:

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)

但是你也可以通过自定义&#34来全新的方向;否则就是#34;机构:

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

没有限制,具有创造性并学习扩展的力量,尝试新的想法,正如您所看到的,人们希望如何编码这些类型的情况有很多变化。 stdlib不能支持这些类型的方法的8种变体而不会混淆。但是每个开发组都可以使用与其编码风格相匹配的扩展。

注意:此版本的完整代码位于帖子的末尾(2)

示例代码1: 以下是&#34;链接&#34;的完整代码版本

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: 以下是&#34的完整代码;否则就是&#34;库(带单元测试):

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
}

答案 1 :(得分:26)

<强>更新

kotlin 1.3现在提供isNullOrEmpty

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

试试这个!很清楚。

var array: List<String>? = null
if (array.orEmpty().isEmpty()) {
    // empty
} else {
    // not empty
}

答案 2 :(得分:5)

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

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

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

when {
    activities?.isNotEmpty() ?: false -> doSomething
    else -> doSomethingElse
}

答案 3 :(得分:5)

更简单的方法是,

if(activities?.isNotEmpty() == true) doSomething() else doSomethingElse()

答案 4 :(得分:2)

如果合适,请考虑使用?.forEach

activities?.forEach {
  doSmth(it)
}

如果你想要你所描述的行为,我认为你的变体读起来比我能想到的更简洁。 (然而简单的if就足够了)

答案 5 :(得分:0)

在我的情况下,price是可选的。我用orEmpty()来处理情况,它返回给定数组或如果给定数组为null则返回一个空数组。

val safeArray  = poi.prices.orEmpty()
if (!safeArray.isEmpty()) {
   ...
}

答案 6 :(得分:0)

在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

答案 7 :(得分:0)

Kotlin 1.3的扩展名为isNullOrEmpty。简短的答案是:

if (activities.isNullOrEmpty) doSomething
else doSomethingElse

扩展名定义为:

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

String和Array存在类似的扩展名。

答案 8 :(得分:-1)

首先,我想建议除了@ mlatu的答案之外还要进行扩展功能,它处理else条件

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

但用法并不那么美好。

其实你正在寻找一个可能的monad