在 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()
我会避免将这些链接起来,因为那样您将用更冗长的内容替换 if
或 when
语句。而且您正在更多地进入我在下面提到的替代方案提供的领域,这是成功/失败情况的完整分支。
注意:这些扩展被推广到所有持有非空值的Collections
的后代。并且不仅仅为列表工作。
替代方案:
Kotlin 的 Result 库提供了一种很好的方法来处理基于响应值的“做这个或那个”的情况。对于 Promises,您可以在 Kovenant 库中找到相同的内容。
这两个库都为您提供了从单个函数返回替代结果的方式,也为您提供了基于结果分支代码的方式。 他们确实要求您控制所采取行动的“答案”的提供者。
这些是 Optional
和 Maybe
的良好 Kotlin 替代品。
进一步探索扩展功能(也许太多了)
本节只是为了表明,当您遇到类似于此处提出的问题的问题时,您可以轻松地在 Kotlin 中找到许多答案,从而按照您想要的方式进行编码。如果这个世界不讨人喜欢,那就改变这个世界。它不是一个好或坏的答案,而是额外的信息。
如果您喜欢扩展函数并想考虑将它们链接到表达式中,我可能会将它们更改如下...
返回this
的withXyz
风格和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?
,可以是true
、false
或null
。要在if
或when
子句中使用表达式,您需要明确检查它是否为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 中处理可为空或空列表的惯用方式的主要内容,如果未能解决你的问题,请参考以下文章
在 joi 和 express-validation 中允许字符串为空或空
java.lang.IllegalArgumentException:用户名不能为空或空,AbstractXMPPConnection.java:484