Say I have a variable activities of type List<Any>?. If the list is not null and not empty, I want to do something, otherwise I want to do something else. I came up with following solution:

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

Is there a more idiomatic way to do this in Kotlin?

推荐答案

For some simple actions you can use the safe call operator, assuming the action also respects not operating on an empty list (to handle your case of both null and empty:

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

For other actions. you can write an extension function -- two variations depending on if you want to receive the list as this or as a parameter:

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)
    }
}

Which you can use as:

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`
    }
}

You can also do inverse function:

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

I would avoid chaining these because then you are replacing an if or when statement with something more wordy. And you are getting more into the realm that the alternatives I mention below provide, which is full branching for success/failure situations.

Note: these extensions were generalized to all descendants of 100 holding non null values. And work for more than just Lists.

Alternatives:

The Result library for Kotlin gives a nice way to handle your case of "do this, or that" based on response values. For Promises, you can find the same thing in the Kovenant library.

这两个库都为您提供了从单个函数返回替代结果的方式,以及根据结果对代码进行分支的方式.They do require that you are controlling the provider of the "answer" that is acted upon.

These are good Kotlin alternatives to Optional and Maybe.

Exploring the extension Functions Further (and maybe too much)

This section is just to show that when you hit an issue like the question raised here, you can easily find many answers in Kotlin to make coding the way you want it to be. If the world isn't likeable, change the world. It isn't intended as a good or bad answer, but rather additional information.

If you like the extension functions and want to consider chaining them in an expression, I would probably change them as follows...

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")
            }
}

Note: full code for this version is at the end of the post (1)

But you could also go a completely new direction with a custom "this otherwise that" mechanism:

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

没有限制,要有创造力,学习扩展的力量,try 新的 idea ,正如你所看到的,人们想要如何编写这些类型的情况有很多不同.stdlib不能支持这些类型的方法的8种变体而不引起混淆.但每个开发组都可以有与其编码风格相匹配的扩展.

Note: full code for this version is at the end of the post (2)

Sample code 1: Here is the full code for the "chained" version:

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
}

Sample Code 2: Here is the full code for a "this otherwise that" library (with unit test):

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
}

Kotlin相关问答推荐

用浮点数或十进制数给出错误答案的阶乘计算

同时也是一个字符串的 Kotlin 枚举

Rabin-Karp字符串匹配的实现(Rolling hash)

返回 kotlin 中的标签和 lambda 表达式

将子元素放在一个列表中

Kotlin 中列表或数组的乘积

在 Kotlin 中使用 @Parcelize 注释时如何忽略字段

Kotlinwhen表达式在使用主题时是否支持复合布尔表达式?

Kotlin 顶级函数与对象函数

Foo::class.java 和 Foo::javaClass 有什么区别?

在 Kotlin 中取最后 n 个元素

Jetpack Compose – LazyColumn 不重组

Kotlin:什么是 kotlin.String!类型

Kotlin 中的内联构造函数是什么?

无法在Kotlin中使用argb color int值?

Kotlin val difference getter override vs assignment

在 Kotlin 中声明 Byte 会出现编译时错误The integer literal does not conform to the expected type Byte

在Kotlin中创建通用二维数组

从 java 活动 *.java 启动 kotlin 活动 *.kt?

Kotlin中默认导入哪些包/函数?