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

Posted

技术标签:

【中文标题】在 Kotlin 中处理可为空或空列表的惯用方式【英文标题】:Idiomatic way of handling nullable or empty List in Kotlin 【发布时间】:2014-12-08 02:10:55 【问题描述】:

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

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

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

【问题讨论】:

注意:when 有两种选择非常接近普通的if 【参考方案1】:

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

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

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

这些是 OptionalMaybe 的良好 Kotlin 替代品。

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

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

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

返回thiswithXyz 风格和whenXyz 应该返回一个新类型,允许整个集合变成一些新类型(甚至可能与原始集合无关)。产生如下代码:

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)

但您也可以使用自定义的“否则那样”机制走向全新的方向:

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

【讨论】:

【参考方案2】:

更新:

kotlin 1.3 现在提供isNullOrEmpty

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


试试这个!很清楚。

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

【讨论】:

其实很优雅! 由于问题要求采用惯用的方式,因此这应该是公认的答案。 ;) 一个缺点是,经过这个检查编译器不知道列表不为空,你将不得不使用数组?。或数组!!。 创建一个val tmpArray = array.orEmpty() 还有isNullOrEmpty()结合了两者【参考方案3】:

更简单的方法是,

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

【讨论】:

isNotEmpty() 已经返回布尔值,无需添加== true。 :) 他需要它,因为activities 可以为空。【参考方案4】:

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

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

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

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

【讨论】:

【参考方案5】:

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

activities?.forEach 
  doSmth(it)

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

【讨论】:

不清楚他只想迭代列表,这个答案只解决了一个用列表“做某事”的用例。【参考方案6】:

在 Kotlin 1.3 中使用的实际方法是 isNullOrEmpty 就像这个答案中提到的那样:https://***.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】:

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

if (activities.isNullOrEmpty) doSomething
else doSomethingElse

扩展定义为:

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

String 和 Array 存在类似的扩展。

【讨论】:

【参考方案8】:

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

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

【讨论】:

【参考方案9】:

首先,我想建议在处理else 条件的@mlatu 的答案之外制作扩展功能

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

但用法没那么漂亮。

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

【讨论】:

以上是关于在 Kotlin 中处理可为空或空列表的惯用方式的主要内容,如果未能解决你的问题,请参考以下文章

检查对象中的值是不是为空或空javascript

在 joi 和 express-validation 中允许字符串为空或空

java.lang.IllegalArgumentException:用户名不能为空或空,AbstractXMPPConnection.java:484

如果它为空或为空,如何从序列化中忽略可为空的属性?

参数绑定的名称不能为空或空!对于命名参数,您需要在 Java 版本上使用 @Param 查询方法参数

评估空或空 JSTL c 标签