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

Kir*_*man 26 idioms kotlin kotlin-null-safety

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

when {
    activities != null && !activities.empty -> doSomething
    else -> doSomethingElse
}
Run Code Online (Sandbox Code Playgroud)

在Kotlin有更实用的方法吗?

Jay*_*ard 31

对于一些简单的动作就可以使用安全调用操作,假设动作也尊重不是一个空洞的名单上运行(处理您的案件空和空:

myList?.forEach { ...only iterates if not null and not empty }
Run Code Online (Sandbox Code Playgroud)

对于其他行动.您可以编写扩展函数 - 两种变体,具体取决于您是要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)
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以用作:

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`
    }
}
Run Code Online (Sandbox Code Playgroud)

你也可以做反函数:

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

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

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

备择方案:

Kotlin 的结果库提供了一种很好的方法来处理基于响应值"执行此操作"或"执行此操作"的情况.对于Promises,您可以在Kovenant库中找到相同的东西.

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

这些是Optional和Kotlin的良好替代品Maybe.

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

本节只是为了表明当你遇到像这里提出的问题一样的问题时,你可以在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")
            }
}
Run Code Online (Sandbox Code Playgroud)

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

但是你也可以通过自定义的"这个其他方面"机制走向全新的方向:

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

没有限制,有创造力并学习扩展的力量,尝试新的想法,正如您所看到的,人们想要如何编码这些类型的情况有很多变化.stdlib不能支持这些类型的方法的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
}
Run Code Online (Sandbox Code Playgroud)

示例代码2: 以下是"此另外"库的完整代码(带单元测试):

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
}
Run Code Online (Sandbox Code Playgroud)


use*_*210 30

更新:

kotlin 1.3 isNullOrEmpty现在提供!

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


试试这个!非常清楚.

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

  • 既然这个问题需要一种惯用的方式,这应该是公认的答案.;) (3认同)
  • 还有 `isNullOrEmpty()` 结合了两者 (2认同)

Kir*_*man 5

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

when {
    activities?.isNotEmpty() == true -> doSomething
    else -> doSomethingElse
}
Run Code Online (Sandbox Code Playgroud)

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

when {
    activities?.isNotEmpty() ?: false -> doSomething
    else -> doSomethingElse
}
Run Code Online (Sandbox Code Playgroud)


Shi*_*rur 5

更简单的方法是,

if(activities?.isNotEmpty() == true) doSomething() else doSomethingElse()
Run Code Online (Sandbox Code Playgroud)

  • 他需要它,因为“activities”可以为空。 (8认同)