Ответ 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
}