Dive into Coding Life

KotlinLearning: 02-2 条件式

08/01/2021

条件式

Kotlinの条件式はif/else式、when式の二種類あります(条件文ではなく条件式)。
Javaの条件文if/else文やswitch文と異なり、Kotlinの条件式は値を返します。
つまりただ条件分岐を実行するのではなく、変数などで戻り値を受け取ることができます。

if/else

真偽値の分岐式です。
与えられた条件が真(true)の場合、偽(false)の場合により処理を分岐します。

if(coffee.include(suger)) {
  "sweet"
} else {
  "bitter"
}

Kotlinのif/elseは式なのでこれは次のようにも記述できます。

val taste = if(coffee.include(suger)) {
  "sweet"
} else {
  "bitter"
}

更に、if/else式の処理が1行しかない場合、三項演算子のように書くことができます。

val taste = if(coffee.include(suger)) "sweet" else "bitter"

Javaでは以下のように書きましたが同じようなものです。

String taste = (coffee.include(suger)) ? "sweet" : "bitter"

ちなみにKotlinには三項演算子はありません。

他の言語同様 else if で条件を追加することもできます。

val suger = 7 //gram
val taste = if(suger > 8) {
  "very sweet"
} else if(suger > 5) {
  "sweet"
} else {
  "bitter"
}

if/else式は上から順に処理されます。
途中のelse ifで条件を満たしてしまうと後続の条件は判定されませんので、数値を扱う場合は順序に気を付ける必要があります。

val suger = 7 //gram
val taste = if(suger > 8) {
  "very sweet"
} else if(suger > 3) {
  "soso"
} else if(suger > 5) {
  "sweet"
} else {
  "bitter"
}
println(taste)

rangeとuntil

Kotlinには値の範囲を表現するkotlin.ranges packageがあります。
Rangesを使うと大なりイコール(>=)小なりイコール(<=)、大なり(>)小なり(<)で表す値の範囲を簡潔に記述できます。

val suger = 7 //gram
val taste = if(suger > 8) {
  "very sweet"
} else if(suger in 3 until 5) {
  "soso"
} else if(suger in 5 .. 8) {
  "sweet"
} else {
  "bitter"
}
println(taste)

Rangesは下限・上限を含みます。
つまり、
suger in 3 .. 5

3 <= sugger <= 5
を表します。

つまり、
suger in 3 .. 5
の下に
suger in 5 .. 8
を記述し、sugger = 5 とした場合、上から順に判定されますので
suger in 3 .. 5
が優先されます。

上限のイコールを除外したい場合は until を使います。
3 until 5

3 <= sugger < 5
と同じ意味を表します。

fun main() {    
    var num = 0
    if (num in 1..10) println("range include $num") else println("range not include $num")
    
    num = 1
    if (num in 1..10) println("range include $num") else println("range not include $num")
    
    num = 10
    if (num in 1..10) println("range include $num") else println("range not include $num")
    if (num in 1 until 10) println("until include $num") else println("until not include $num")
}

when式

if/else と else/if を組み合わせると条件が複雑になり、一目で指定している条件を理解することが難しくなり勝ちです。
そのため複数の条件を判断する必要がある場合、when式の使用をオススメします。

when (type) {
  "A" -> println("typeA")
  "B" -> println("typeB")
  "C" -> println("typeC")
}

when式はJavaのswitch文のような位置づけですが、break が不要でより簡潔に、間違いが少なくなるよう記述できるとされています。
上述の例をwhen式で記述すると、

val suger = 4 //gram
val taste = when(suger) {
  in 0 until 3 -> "bitter"
  in 3 until 5 -> "soso"
  in 5 .. 8 -> "sweet"
  else -> "very sweet"
}
println(taste)

となります。
条件 -> 結果
という書き方なので視認性が上がっていると言えそうです。

条件に当てはまらなかった場合の処理はelseを用いることにより指定することができますが、あくまで条件に当てはまらなかった場合という処理になります。
つまり上記例の場合、when式のelseで8以上という値を想定していますが、何らかの不具合によりsugerが0以下の値になっている可能性もあります。
厳密にチェックが必要な場合は if/else式と組み合わせる等工夫が必要になります。

またwhen式の分岐選択肢はコンマで区切り、1つの分岐に複数の値をまとめることができます。

fun isBlack(taste: Taste) = when(taste) {
  Taste.VERYSWEET, Taste.SWEET, Taste.SOSO-> false
  Taste.BITTER-> true
}

その他、when式も上から順に分岐選択肢を確認し処理していきますので、分岐選択肢の順序には注意が必要です。

when式の条件に使用できるオブジェクト

Kotlinのwhen式はJavaのswitchと比較すると遥かに強力で、どのようなオブジェクトも条件に利用できます。
各分岐選択肢ではオブジェクトが等しいかどうかがチェックされます。

fun brendType(c1: CoffeeBean, c2: CoffeeBean) =
  when(setOf(c1, c2)) {
    setOf(KENYA, AFRICA) -> BREND1
    setOf(AFRICA, ETHIOPIAN) -> BREND2
    setOf(ETHIOPIAN, KENYA) -> BREND3
    else -> throw Exception("Others")
  }

Kotlinの標準ライブラリーには引数で与えられたオブジェクトを含んだコレクションSetを生成するsetOf関数があります。
Setは順序を問わないコレクションであり、ここではSetの中のオブジェクトが順序を問わず同じであるかがチェックされます。
whenの分岐選択肢には式を書くことも出来ます。

引数のないwhen

when式は引数なしでも使用することができます。
その場合、分岐選択肢はbool式になります。

fun brendType(c1: CoffeeBean, c2: CoffeeBean) =
  when {
    (c1 == KENYA && c2 == AFRICA) || (c1 == AFRICA && c2 == KENYA) -> BREND1
    (c1 == AFRICA && c2 == ETHIOPIAN) || (c1 == ETHIOPIAN && c2 == AFRICA) -> BREND2
    (c1 == ETHIOPIAN) && c2 == KENYA) || (c1 == KENYA && c2 == ETHIOPIAN) -> BREND3
	else -> throw Exception("Others")
  }

when式は値が等しいかだけではなく、型チェックなども分岐選択肢に使うことができます。

when(e) {
  is NotFoundException -> e.message //NotFoundExceptionにスマートキャスト
  is NumberFormatException -> e.message //NumberFormatExceptionにスマートキャスト
  else -> throw IllegalArgumentException()
}

if/else式やwhen式で{}を使用する場合、最後の式が戻り値になります。
このルールはtry/catchやラムダ式でも同様です。
ログ出力のコードなどを埋め込む場合は、その処理が最後に来ないよう順序に注意しましょう。