web-dev-qa-db-ja.com

Kotlinでnull可能または空のリストを処理する慣用的な方法

タイプList<Any>?の変数activitiesがあるとします。リストがnullでも空でもない場合は、何かをしたいのですが、それ以外の場合は別のことをしたいと思います。私は次の解決策を思いつきました:

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

Kotlinでこれを行うためのより慣用的な方法はありますか?

30
Kirill Rakhman

一部の単純なアクションでは、安全な呼び出し演算子を使用できます。アクションは空のリストでの操作も考慮していないと想定します(both nullおよび空のケースを処理するため)。

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

他のアクションのため。拡張関数を作成できます。リストをthisとして受け取るか、パラメータとして受け取るかによって、2つのバリエーションがあります。

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ステートメントをより冗長なものに置き換えるので、これらをチェーンすることは避けます。そして、あなたは私が以下で言及する代替案が提供する領域にもっと入ります、それは成功/失敗の状況のた​​めの完全な分岐です。

注:これらの拡張は、null以外の値を保持するCollectionsのすべての子孫に一般化されました。そして、単なるリスト以上のもののために働きます。

代替案:

Kotlinの Result ライブラリーは、応答値に基づいて「これを実行するか、それを実行する」というケースを処理する優れた方法を提供します。 Promiseについては、同じことが Kovenant ライブラリにあります。

これらのライブラリはどちらも、単一の関数から別の結果を返す方法と、結果に基づいてコードを分岐する方法を提供します。 彼らはあなたが作用する「答え」の提供者を管理していることを必要とします。

これらは、OptionalおよびMaybeのKotlinに代わる優れた手段です。

さらに拡張機能を探る(そして多すぎる)

このセクションは、ここで出された質問のような問題にぶつかったときに、Kotlinで多くの答えを簡単に見つけて、思い通りのコーディングを作成できることを示しています。世界が気に入らなければ、世界を変えてください。これは良い答えでも悪い答えでもありませんが、追加情報です。

拡張関数が好きで、式でそれらをチェーンすることを検討したい場合は、おそらく次のように変更します...

withXyzフレーバーは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:これは、「this other 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
}
30
Jayson Minard

UPDATE:

kotlin 1.3はisNullOrEmptyを提供します!

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


これを試して!非常に明確な。

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

他の回答に加えて、セーフコール演算子を拡張メソッドisNotEmpty()と組み合わせて使用​​することもできます。安全な呼び出しのため、戻り値は実際にはBoolean?は、truefalse、またはnullのいずれかです。 ifまたはwhen句で式を使用するには、trueであるかどうかを明示的に確認する必要があります。

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

Elvis演算子を使用した代替構文:

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

より簡単な方法は、

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

必要に応じて、?.forEachの使用を検討してください

activities?.forEach {
  doSmth(it)
}

あなたが説明した振る舞いを正確に望むなら、あなたのバリアントは私が考えることができる他のより簡潔なものよりもよく読めると思います。 (ただし、単純なifで十分です)

3
defhlt

Kotlin 1.3で使用する実際の方法は、この回答で述べたようにisNullOrEmptyです https://stackoverflow.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
0
gil.fernandes

私の場合、価格はオプションです。次のようにorEmpty()を使用してケースを処理します。これは、指定された配列またはnullの場合は空の配列を返します。

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

Kotlin 1.3の拡張子はisNullOrEmptyです。簡単な答えは:

if (activities.isNullOrEmpty) doSomething
else doSomethingElse

拡張は次のように定義されます。

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

StringおよびArrayにも同様の拡張が存在します。

0
Vadim