[Kotlin] whenの使い方

Kotlinでは、switch/caseの代わりにwhenという構文を使います。 whenは最初構文のサンプルを見た時にすぐ使えるなと思ったのですが、いざアプリを作っていく上で直面する色々な要求に当てはめて使い始めると戸惑うことが多く、まとまった例文がなくて苦労したので今回まとめて見ました。



基本的な使い方

switch/caseでは下のように表記していた構文が

switch (company) {
    case "Apple":
        platform = "iOS";
        break;
    case "Google":
        platform = "Android";
        break;
    default:
        platform = "Unknown Platform"
}

whenでは下のようになります。

platform = when (company) {
    "Apple" -> "iOS"
    "Google" -> "Android"
    else -> "Unknown Platform"
}

ざっくりと述べるとswitch/caseから下のように変わりました。

上のwhenの例を関数の中で使うと以下のようになります。when自体が結果を返す式となっているのがよりわかると思います。

private fun platformName(company: String): String {
    return when (company) {
        "Apple" -> "iOS"
        "Google" -> "Android"
        else -> "Unknown Platform"
    }
}

同じコードをJavaのswitch/caseで書くと下のようになります。

private String platformName(String company){
    String platform;
    switch (company) {
        case "Apple":
            platform = "iOS";
            break;
        case "Google":
            platform = "Android";
            break;
        default:
            platform = "Unknown Platform"
    }
    return platform;
}



各ケースの返す値を式にする方法

上の例では条件に該当した場合(文字列が同じであった場合)に文字列を返すという例でしたが、条件に該当した場合に式の実行結果を返すこともできます。

private fun validateLength(textType: TextType, text: String): Boolean{
     val result = when (textType) {
        TextType.NICKNAME -> text.length in 1..9
        TextType.PHONE_NUMBER -> text.length ==  11
        TextType.PASSWORD -> text.length > 5
    }
    return result
}

このコードでは入力のテキストタイプがニックネームか、電話番号か、もしくはパスワードかによってテキストの長さが正しいかを->の右辺で判定しそれをそのまま結果として返しています。

参考までに上のコードは下のように書くこともできます。

private fun validateLength2(textType: TextType, text: String): Boolean{
    when (textType) {
        TextType.NICKNAME -> return text.length in 1..9
        TextType.PHONE_NUMBER -> return text.length ==  11
        TextType.PASSWORD -> return text.length > 5
    }
}

また関数を表現的な形式に書き換えて下のようにしても振る舞いは全く同じになります。

private fun validateLength3(textType: TextType, text: String) = when (textType) {
        TextType.NICKNAME -> text.length in 1..9
        TextType.PHONE_NUMBER -> text.length ==  11
        TextType.PASSWORD -> text.length > 5
}  

1行以上の処理を各ケースで行いたいとき

上の例では各TextTypeごとにBooleanの値を返す1行の式を実行しましたがもし各TextTypeごとに行いたい処理が複数あったらどうしたら良いでしょうか? その場合は{}で囲って下のように書くことができます。

private fun validateText(textType: TextType, text: String): Boolean{
        when (textType) {
            TextType.NICKNAME -> {
                if(text.isEmpty()){
                    Log.d(TAG, "Nickname length is empty.")
                    return false
                }

                if(text.length > 9){
                    Log.d(TAG, "Nickname is too long.")
                    return false
                }

                return true
            }
            TextType.PHONE_NUMBER -> {
                if(text.length ==  11){
                    return true
                }else{
                    Log.d(TAG, "Phone number is not 11 digits.")
                    return false
                }
            }
            TextType.PASSWORD -> {
                return text.length > 5 //You can wrap 1 line expression off course.
            }
        }
    }

この例ではテキストの長さのチェックに加えて、ログの出力も行なっています。

単純なマッチングではない方法でケースわけしたい時

今までの例ではwhen(type)で引数に私たtypeが各タイプと==(イコール)であるかどうかで場合分けをしてきましたが、イコール判定以外でケースわけをしたい時にもwhenを使うことができます。
こうしたい場合は、when(type)のようにパラメータを渡さずに下のように記述します。

private fun processFilePath(filePath: String){
    when{
        filePath.startsWith("/image") -> {
            filePath.split("/").last().let{ filename ->
                loadPhoto(filename)
                return
            }
        }

        filePath.contains("music", false) -> {
            filePath.split("/").last().let{ filename ->
                playMusic(filename)
                return
            }
        }

        filePath.endsWith("data.xml") -> {
            filePath.split("/").last().let{ filename ->
                readData(filename)
                return
            }
        }
    }
}

これはこのメソッドの引く数であるfilePathがどんなタイプのものかをwhenの中で判定しています。

Rangeで分けたい場合

番外編として下のようにRangeで分けることもできます。

when (value) {
    in 0..9 -> {

    }
    in 10..99 -> {

    }
    in 100..999 -> {

    }
}

ただしvalueが浮動小数点の時は、Rangeでやると隙間を間違って作ってしまいがちです。

when (value) {
    in 0.0..0.49 -> {

    }
    in 0.5..1.0 -> {

    }
    //0.495が漏れる
}

そういう時は下のように対処してもいいかもしれません。

when {
    value >= 0.5 -> {

    }
    value >= 0.0  -> {
        
    }    
}

まとめ

whenは使いこなすと非常に読みやすいコードがかけるので皆さんも条件分岐の必要性がある箇所で積極的に使って見てください。
(上記の方法でできないケースがあったら是非我々に教えてください!)



チュートリアルの内容を自分のプロジェクトに反映させる時にわからないことや問題にぶつかった時のために、Fourhandsではオンラインのメンタリングサービスを提供しています。
ビデオチャットやスクリーン共有をつかて実際にコードをメンターに見てもらいながら問題の解決や、考え方のアドバイスがもらえるサービスですので、是非活用して見てください。
fourhands.com