この記事はMicroAd Advent Calendar 2019の1日目の記事です。
状況
「ListやSetと言ったCollectionから要素を1つ取り出す」場合、getメソッドやfirst関数を使いたくなります。
val list: List<String> = listOf("a", "b", "c")
val first: String? = list.first { it == "d" }
val secondElement: String? = list.get(1) // 実際は[]を用いることができるが、説明のためgetを用いる
しかし、これらの処理は、該当する要素が見つからなかった(e.g. emptyだった、検索条件に引っかからなかった、要素以上のインデックスだった、等)場合、それぞれNoSuchElementExceptionとIndexOutOfBoundsExceptionを投げます。
つまり、「見つからなかったらnullにする(or した上でnullだった時の処理を入れる)」的な処理に使うことはできません。
サンプルコードの通り、これらの取得結果をnullable型に代入しても警告やエラーにならないため、深いバグになりやすい点でも注意が必要です。
対処
(条件分岐や例外処理をしない形での)Collectionの要素を安全な取り出しは、nullやデフォルト値を返す関数を用いることで実現できます。
ここでは具体的に以下の3種類を紹介します。
-
~OrElse系 -
~OrNull系 -
find系
「~系」としてまとめていますが、これらは関数によって有ったりなかったりします。
その具体的な有無に関してはソースコードをご覧下さい。
~OrElse系関数を用いる
見つからなければデフォルト値を返すのが~OrElse系関数です。
getOrElseやelementAtOrElse系が有ります。
この~OrNull系関数は、以下のように、デフォルトの値生成にInt -> デフォルト値のラムダを取ります。
val index // 取得対象インデックス想定
list.getOrElse(index) { i -> // iにはindexが入ってくる
// デフォルト値の生成処理
}
~OrNull系関数を用いる
見つからなければnullを返すのが~OrNull系関数です。
firstOrNullやgetOrNullなどが有ります。
find系関数を用いる
~OrNull系関数と同じく、見つからなければnullを返すのがfind系関数です。
findとfindLast関数が有ります。
内容的にはそれぞれfirstOrNullとlastOrNullを呼び出しています。
終わりに
この記事では条件分岐や例外処理をしない形でCollectionの要素を安全に取り出す方法についてまとめました。
この記事を書いたきっかけは、チームの人間が何人も「JavaのStream APIのfindFirstメソッドとfirst関数の挙動が違う」という罠を踏んだことです。
しかも前述の通りnullable型に代入しても警告やエラーにならないため、ヒヤッとする所に取り残してしまうことも有りました。
get系はともかく、何か検索して取ってくるような処理では「見つからなければnullまたはデフォルト値」を返すだけでいい気がするんですが、何でそうなっていないんでしょうか……。
ともあれ、この記事がCollectionの要素を安全に取り出すことに役立てば幸いです。