Идиоматический способ обращения с нулевым или пустым списком в Котлин

Скажем, у меня есть переменная activities типа List<Any>?. Если список не пустой и не пустой, я хочу что-то сделать, иначе я хочу сделать что-то еще. Я придумал следующее решение:

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

Есть ли более идиоматический способ сделать это в Котлине?

Ответ 1

Для некоторых простых действий вы можете использовать оператор безопасного вызова, предполагая, что действие также учитывает отсутствие работы с пустым списком (для обработки вашего случая как нулевого, так и пустого:

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 дает хороший способ справиться с вашим случаем "сделать это или то" на основе значений ответов. Для Обещаний вы можете найти то же самое в библиотеке Ковенанта.

Обе эти библиотеки позволяют вам возвращать альтернативные результаты из одной функции, а также разветвлять код на основе результатов. Они требуют, чтобы вы контролировали поставщика "ответа", который принимается во внимание.

Это хорошие котлинские альтернативы Optional и Maybe.

Дальнейшее изучение функций расширения (и, возможно, слишком много)

Этот раздел просто для того, чтобы показать, что когда вы сталкиваетесь с такой проблемой, как вопрос, поднятый здесь, вы можете легко найти много ответов в Kotlin, чтобы сделать кодирование таким, каким вы хотите его видеть. Если мир не приятен, измени мир. Это не хороший или плохой ответ, а скорее дополнительная информация.

Если вам нравятся функции расширения и вы хотите рассмотреть их в выражении, я, вероятно, изменил бы их следующим образом...

wiспасибоyz возвращают this а 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'
    }
}

Нет никаких ограничений, проявляйте творческий подход и изучайте возможности расширений, пробуйте новые идеи, и, как вы можете видеть, существует множество вариантов того, как люди хотят кодировать ситуации такого типа. 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
}

Пример кода 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
}

Ответ 2

ОБНОВИТЬ:

kotlin 1.3 предоставьте isNullOrEmpty сейчас!

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


попробуй это! очень ясно.

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

Ответ 3

В дополнение к другим ответам вы также можете использовать оператор безопасного вызова в сочетании с методом расширения isNotEmpty(). Из-за безопасного вызова возвращаемое значение фактически Boolean?, которое может быть true, false или null. Чтобы использовать выражение в предложении if или when, вам нужно явно проверить, не является ли оно true:

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

Альтернативный синтаксис с использованием оператора elvis:

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

Ответ 4

Более простой способ был бы,

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

Ответ 5

Рассмотрите возможность использования ?.forEach при необходимости

activities?.forEach {
  doSmth(it)
}

Если вы хотите точно описывать поведение, которое вы описали, я думаю, что ваш вариант лучше читает, чем что-либо еще более сжатое, о чем я могу думать. (Однако достаточно простого if)

Ответ 6

В моем случае цены являются необязательными. Я обрабатываю случай следующим образом с помощью orEmpty() который возвращает данный массив или пустой массив, если данный массив равен нулю.

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

Ответ 7

Фактический метод, используемый в Kotlin 1.3, похож на isNullOrEmpty, как было упомянуто в этом ответе: fooobar.com/questions/52576/...

Вот пример его использования:

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

Ответ 8

Kotlin 1.3 имеет расширение isNullOrEmpty. Краткий ответ:

if (activities.isNullOrEmpty) doSomething
else doSomethingElse

Расширение определяется как:

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

Аналогичное расширение существует для String и Array.

Ответ 9

Во-первых, я хотел бы посоветовать сделать функцию расширения в дополнение к ответу @mlatu, который обрабатывает условие else

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

Но использование не так красиво.

На самом деле вы ищете монашескую монаду.