Kotlin 三項演算子の代替 if/when式による条件分岐


Kotlinにおける三項演算子の代替 if/when式による条件分岐 詳細解説

はじめに:他の言語の三項演算子とKotlinのアプローチ

多くのプログラミング言語、特にC、C++、Java、JavaScriptなどでは、条件に基づいて二つの値のうち一つを選択する際に便利な「三項演算子」(Ternary Operator)が提供されています。これは通常、条件 ? 真の場合の値 : 偽の場合の値 という形式を取ります。例えばJavaでは、次のように使われます。

java
int a = 10;
int b = 20;
int max = (a > b) ? a : b; // a > b が真なら a を、偽なら b を max に代入
System.out.println(max); // 出力: 20

この三項演算子は、簡単な条件分岐で値を決定する際にコードを短く記述できるため、多くの開発者に利用されています。

しかし、Kotlinには、JavaやC++のような直接的な「三項演算子」は存在しません。これはKotlinの設計思想に基づいています。Kotlinでは、他の言語でステートメント(文)として扱われることが多い if が「式」(Expression)として設計されており、その結果として値を返すことができるため、三項演算子が必要ないと考えられています。

この記事では、Kotlinにおける条件分岐の主要な手段である if 式と when 式に焦点を当て、それぞれがどのように三項演算子の代替となるのか、またそれらが提供する柔軟性と表現力について詳細に解説します。約5000語をかけて、基本構文から応用例、使い分け、さらには関連する高度なトピックまで、深く掘り下げていきます。Kotlinでの効果的な条件分岐の記述方法をマスターし、より簡潔で可読性の高いコードを書くための知識を身につけましょう。

なぜKotlinには三項演算子がないのか? 「if」が式であることの重要性

Kotlinに三項演算子がない最大の理由は、前述したように if が「式」であるという特性にあります。多くの言語では if は「ステートメント」であり、特定の処理を実行しますが、値を直接返しません。そのため、条件によって異なる値を代入したい場合は、以下のように if ステートメントと変数代入を組み合わせて記述する必要がありました。

java
// Javaの例 (ifはステートメント)
int value;
if (condition) {
value = value_if_true;
} else {
value = value_if_false;
}

これをより簡潔に記述するために、Javaでは三項演算子が導入されました。

java
// Javaの例 (三項演算子)
int value = condition ? value_if_true : value_if_false;

一方、Kotlinでは if が式であるため、評価結果として値を返します。これにより、以下のように直接変数に代入できます。

kotlin
// Kotlinの例 (ifは式)
val value = if (condition) value_if_true else value_if_false

このKotlinの if 式の構文は、Javaの三項演算子 condition ? value_if_true : value_if_false と機能的に全く同じです。Javaの三項演算子よりも if (...) ... else ... という自然言語に近い表現であるため、多くの開発者にとって直感的で分かりやすいと感じられる傾向があります。

Kotlinの設計思想には、「冗長性の排除」と「可読性の向上」があります。三項演算子は簡潔さをもたらしますが、複雑な条件やネストが発生すると、かえって可読性が低下することがあります。Kotlinでは if を式とすることで、三項演算子と同じ簡潔さを実現しつつ、if-else 構文による高い可読性を保っています。特に、else if を含む複数の条件分岐の場合、Javaの三項演算子をネストすると非常に読みにくくなりますが、Kotlinの if-else if-else 式は比較的読みやすいままです。

つまり、Kotlinには三項演算子という名前の演算子はありませんが、その機能は if 式によって完全に代替されており、かつKotlinらしいシンプルさと可読性を実現しているのです。

if 式による条件分岐

Kotlinの if 式は、最も基本的な条件分岐の手段です。前述のように、これは単なるステートメントではなく、値を返す「式」として機能します。

基本構文

if 式の基本構文は、他の多くの言語と同様に if (...) { ... } else { ... } の形を取ります。

kotlin
if (condition) {
// condition が真の場合に実行されるブロック
} else {
// condition が偽の場合に実行されるブロック
}

波括弧 {} は、ブロック内のコードが1行のみの場合は省略できます。

kotlin
if (condition) singleExpression() else anotherExpression()

if 式としての利用(値を返す)

if 式が真価を発揮するのは、その評価結果を値として利用する場合です。if 式全体の評価結果は、対応するブロック内の最後の式の値になります。

“`kotlin
val a = 10
val b = 20

val max = if (a > b) {
println(“a is greater than b”)
a // この式の値が max に代入される
} else {
println(“b is greater than or equal to a”)
b // この式の値が max に代入される
}

println(“Max value: $max”) // 出力: b is greater… と 20
“`

このように、波括弧 {} で囲まれたブロック内では、最後の行に記述された式の値がそのブロック全体の評価結果となります。ブロックが複数行にわたる場合でも、最後の行が重要です。

もし波括弧を省略して1行で記述する場合、その1行の式が if 式全体の評価結果となります。

“`kotlin
val a = 10
val b = 20

val max = if (a > b) a else b // a > b が真なら a を、偽なら b を返す

println(“Max value: $max”) // 出力: 20
“`

これは、Javaの三項演算子 (a > b) ? a : b と全く同じ機能を持ち、Kotlinにおける三項演算子の主要な代替手段となります。

else if

複数の条件を順次チェックしたい場合は、else if 句を使用します。

“`kotlin
val score = 85
val grade = if (score >= 90) {
“A”
} else if (score >= 80) {
“B”
} else if (score >= 70) {
“C”
} else {
“D or F”
}

println(“Score: $score, Grade: $grade”) // 出力: Score: 85, Grade: B
“`

この if-else if-else 構造も、式として値を返すことができます。各ブロックの最後の式が、その条件が満たされた場合の if 式全体の評価結果となります。

else 句の扱い

if 式をステートメントとして(つまり、値をどこにも代入せず、単に副作用のために)使用する場合、else 句は省略可能です。

kotlin
val debugMode = true
if (debugMode) {
println("Debugging enabled") // debugMode が true の場合のみ実行
}
// else 句がない場合、condition が偽でも何も実行されない

しかし、if 式を式として(つまり、値を返す目的で)使用する場合、通常は else 句が必須となります。これは、if 式が常に何らかの値を返さなければならないためです。else 句がない場合、条件が偽だったときに返す値が決定できないため、コンパイルエラーとなるか、あるいは Unit 型(Kotlinの「何もない」ことを示す型)を返すことになり、意図しない結果を招く可能性があります。

“`kotlin
// val result = if (someCondition) someValue // else がないとコンパイルエラー (式として使う場合)

// 正しい例:else 句を含める
val result = if (someCondition) someValue else otherValue
“`

ただし、if 式の戻り値の型が Unit である場合は、else 句を省略できます。これは、if ブロック内で Unit を返す式(例えば println() の呼び出しや、代入ステートメントなど)のみを実行し、else の場合は何も実行しない(結果として Unit を返す)という状況です。しかし、値を計算して変数に代入するといった目的で if 式を使う場合は、常に else 句を含めるべきです。

kotlin
// 式だが、戻り値が Unit なので else は省略可能
val printResult = if (condition) {
println("Condition is true")
} // else がなくても OK。条件が偽なら Unit を返す。

if 式の利点

  • 可読性: Javaの三項演算子よりも自然な構文で、特に条件が少し複雑になる場合や else if を含む場合に読みやすいです。
  • 式としての柔軟性: 値を直接変数に代入したり、関数の引数として渡したり、他の式の一部として使用したりできます。これにより、コードをより簡潔かつ関数型スタイルに近づけることができます。
  • 波括弧の省略: 単純な1行の式の場合は波括弧を省略でき、三項演算子に近いコンパクトさを得られます。
  • 型の自動推論: if 式が返す値の型は、コンパイラが自動的に推論します。異なる型の値を返す可能性がある場合、共通のスーパータイプ(例えば Any)が型として推論されます。

kotlin
val value = if (isError) "Error message" else 123 // value の型は Any に推論される

if 式の欠点/限界

  • 多数の条件分岐: else if が多数連なる場合、コードが縦に長くなり、可読性が低下する可能性があります。このようなケースでは、後述する when 式の方が適していることが多いです。
  • 特定の値に対する分岐: 単一の変数や式の値に対して、特定の値(例えば 1, 2, “success” など)や値の範囲に基づいて分岐したい場合、if 式を多数の else if で書くのは冗長です。これも when 式が得意とする領域です。

when 式による条件分岐

Kotlinの when 式は、他の言語の switch ステートメントに似ていますが、より強力で柔軟性があり、こちらも「式」として値を返すことができます。主に、単一の主体(変数や式)の値に基づいて複数の分岐を行いたい場合に非常に有効です。

基本構文

when 式の基本構文には、主体を指定する場合と指定しない場合があります。

主体を指定する場合:

kotlin
when (subject) {
value1 -> action1()
value2 -> {
action2_1()
action2_2()
}
value3, value4 -> action3_or_4() // 複数の値をカンマで区切って指定
else -> defaultAction() // いずれの条件にも一致しない場合
}

subject は任意の式で、その評価結果が各条件(-> の左側)と比較されます。条件が一致した最初の分岐ブロック(-> の右側)が実行されます。

主体を指定しない場合:

主体を指定しない when は、一連のブール式を順次評価し、真になった最初の式に対応するブロックを実行します。これは、複数の条件を組み合わせた複雑な if-else if チェーンの代替として非常に強力です。

kotlin
when {
condition1 -> action1() // condition1 が真の場合
condition2 && condition3 -> action2() // condition2 かつ condition3 が真の場合
someFunction() -> action3() // someFunction() の結果が真の場合
else -> defaultAction() // いずれの条件も真でない場合
}

主体なしの when は、if-else if 構造と機能的に非常に似ていますが、when の構文を使うことで、条件の羅列がより構造的に見え、可読性が向上することがあります。

when を式として利用(値を返す)

if 式と同様に、when 式もその評価結果を値として返すことができます。各分岐ブロックの評価結果は、そのブロック内の最後の式の値になります。

“`kotlin
val statusCode = 200

val message = when (statusCode) {
200 -> “OK” // “OK” が message に代入される
400 -> “Bad Request”
401, 403 -> “Authentication Error” // 401 または 403 の場合
in 500..599 -> “Server Error” // 500から599の範囲の場合
else -> “Unknown Status” // いずれにも一致しない場合
}

println(“Status $statusCode: $message”) // 出力: Status 200: OK
“`

主体なしの when 式も値を返すことができます。

“`kotlin
val x = 10
val y = 5

val description = when {
x > y -> “x is greater than y”
x < y -> “x is less than y”
else -> “x is equal to y”
}

println(description) // 出力: x is greater than y
“`

さまざまな条件

when 式の強力な点は、単なる定数値の比較だけでなく、さまざまな種類の条件を指定できることです。

  • 定数: 最も一般的な形式です。数値、文字列、列挙定数など、比較可能なあらゆる型の定数を指定できます。

    kotlin
    val color = "red"
    when (color) {
    "red" -> println("Stop")
    "yellow" -> println("Caution")
    "green" -> println("Go")
    else -> println("Unknown color")
    }

  • 複数の定数: カンマ , で区切って複数の値を指定できます。

    kotlin
    val dayOfWeek = 6 // 土曜日
    when (dayOfWeek) {
    6, 7 -> println("Weekend") // 土曜日または日曜日
    in 1..5 -> println("Weekday") // 月曜日から金曜日
    else -> println("Invalid day")
    }

  • 範囲 (in): in 演算子とレンジ (..) を使って、数値や文字の範囲を指定できます。

    kotlin
    val age = 35
    val group = when (age) {
    in 0..12 -> "Child"
    in 13..19 -> "Teenager"
    in 20..64 -> "Adult"
    else -> "Senior"
    }
    println("Age $age is in group: $group") // 出力: Age 35 is in group: Adult

    !in 演算子を使って、範囲に含まれない場合を指定することも可能です。

    kotlin
    val number = 5
    when (number) {
    !in 1..10 -> println("Number is outside 1..10")
    else -> println("Number is within 1..10")
    }

  • 型 (is): is 演算子を使って、主体の型が特定の型であるかどうかをチェックできます。スマートキャストが適用されるため、型チェック後にキャストなしでメンバーにアクセスできます。

    “`kotlin
    fun process(obj: Any) {
    when (obj) {
    is String -> println(“Input is a String of length ${obj.length}”) // obj は String にスマートキャストされる
    is Int -> println(“Input is an Int with value ${obj * 2}”) // obj は Int にスマートキャストされる
    is Boolean -> println(“Input is a Boolean: $obj”)
    else -> println(“Unknown type”)
    }
    }

    process(“Hello”) // 出力: Input is a String of length 5
    process(100) // 出力: Input is an Int with value 200
    process(true) // 出力: Input is a Boolean: true
    “`

    !is 演算子を使って、特定の型ではない場合を指定することも可能です。

  • 任意のブール式(主体なし when: 前述のように、主体なしの when は、各分岐に任意のブール式を指定できます。

    kotlin
    val hour = 14
    when {
    hour < 12 -> println("Morning")
    hour < 18 -> println("Afternoon")
    else -> println("Evening")
    }

    これは、複雑な if-else if チェーンをより構造的に記述するのに役立ちます。

  • 関数の呼び出しなど: -> の左側には、主体の値と比較可能な任意の式を指定できます。例えば、関数の戻り値と比較することも可能です。

    “`kotlin
    fun calculateThreshold(): Int = 50

    val value = 60
    when (value) {
    calculateThreshold() -> println(“Value equals threshold”)
    else -> println(“Value does not equal threshold”)
    }
    “`

else 句の扱い

when 式を式として(つまり値を返す目的で)使用する場合、else 句が必須となることが多いです。これは、if 式と同様に、すべての可能なケースを網羅し、常に何らかの値を返すことを保証するためです。コンパイラは、when 式がすべての可能な入力値を扱っているかどうかをチェックします。

else 句が省略可能なのは、以下のいずれかの条件を満たす場合です。

  1. when 式がステートメントとして(値をどこにも代入せず、単に副作用のために)使用されている場合。
  2. when 式の主体の型が sealed class または sealed interface であり、すべての可能なサブクラス/実装が when の分岐で網羅されている場合。この場合、コンパイラが網羅性をチェックできるため、else 句は不要になります。

    “`kotlin
    sealed class Result
    class Success(val data: String) : Result()
    class Error(val code: Int) : Result()

    fun handleResult(result: Result) {
    when (result) { // 主体 result の型が sealed class Result
    is Success -> println(“Success: ${result.data}”) // Success を網羅
    is Error -> println(“Error: ${result.code}”) // Error を網羅
    // すべての sealed なサブクラスを網羅しているので、else 句は不要
    }
    }
    “`

値を返す目的で when 式を使用する際は、多くの場合 else 句を記述して、網羅性を保証し、意図しない Unit やその他のデフォルト値が返されるのを防ぐべきです。

when 式の利点

  • 多数の条件分岐の可読性: 単一の主体に対する多数の分岐を、if-else if よりもはるかに構造的かつ読みやすく記述できます。
  • 多様な条件タイプ: 定数、複数の値、範囲、型など、さまざまな種類の条件を簡潔に記述できます。これにより、複雑な条件分岐ロジックを整理できます。
  • 式としての柔軟性: if 式と同様に、値を返すことができるため、簡潔なコード記述や関数型スタイルへの適応が容易です。
  • スマートキャストとの連携: is 条件を使用した場合、コンパイラが自動的に対象の変数をその型にスマートキャストするため、余分なキャスト記述が不要になり、コードがすっきりします。
  • 網羅性の保証: 式として使用する場合や、sealed 型を主体とする場合、コンパイラが網羅性をチェックしてくれるため、すべてのケースが適切に処理されていることを確認しやすくなります(特に else 句が必須の場合や sealed 型の場合)。

when 式の欠点/限界

  • シンプルな真偽判定: 単純な true/false の条件分岐だけの場合は、if 式の方が簡潔です。例えば when (condition) { true -> ... false -> ... } と書くよりも if (condition) ... else ... と書く方が一般的です。
  • 主体の評価: 主体を指定する when 式では、主体が一度評価されます。主体が複雑な計算を含む場合や副作用を持つ場合、その点に注意が必要です。

if 式と when 式の使い分け

Kotlinにおける三項演算子の代替として、if 式と when 式のどちらを使うかは、条件分岐の性質によって判断するのが最適です。以下に、使い分けのガイドラインを示します。

  1. シンプルな真偽判定や少ない条件:

    • ifが最適です。
    • 例: val result = if (x > 0) "Positive" else "Non-positive"
    • 例: val value = if (condition1) value1 else if (condition2) value2 else value3else if が2〜3個程度なら可読性は保たれます)
  2. 単一の変数や値に対する多数の分岐:

    • whenが最適です。
    • 例: エラーコードに応じたメッセージの表示、ステータスに応じた処理の振り分け。
    • when (statusCode) { 200 -> ..., 400 -> ..., 500 -> ..., else -> ... }
  3. 値の範囲や型のチェックを含む分岐:

    • whenが非常に便利です。
    • 例: 年齢層による分類 (in 0..12)、受け取ったオブジェクトの型による処理分け (is String, is Int)。
    • when (age) { in 0..12 -> ..., in 13..19 -> ..., else -> ... }
    • when (obj) { is String -> ..., is Int -> ..., else -> ... }
  4. 複数の変数や複雑な条件を組み合わせた分岐:

    • 主体なしの whenまたは複雑な if-else if チェーン
    • 主体なしの when は、各条件が独立したブール式である場合に、リストアップされた条件のように見え、可読性が向上することがあります。
    • when { user.isLoggedIn && user.hasPermissions -> ..., user.isGuest -> ..., else -> ... }
    • if (user.isLoggedIn && user.hasPermissions) { ... } else if (user.isGuest) { ... } else { ... }
    • どちらを選ぶかは個人の好みやチームのコーディング規約にもよりますが、主体なし when は条件が多くなる場合に視覚的に整理しやすい傾向があります。
  5. 式として値を返す必要がある場合:

    • どちらも可能です。上記のガイドラインに従って、条件の性質に合った方を選びます。式として使う場合は、ほとんどの場合 else 句が必須となることを忘れないでください(whensealed 型の組み合わせを除く)。

比較例

例1: 数値の正負判定

  • if 式:

    kotlin
    val num = 10
    val sign = if (num > 0) "Positive" else if (num < 0) "Negative" else "Zero"

  • when 式(主体あり):

    kotlin
    val num = 10
    val sign = when {
    num > 0 -> "Positive"
    num < 0 -> "Negative"
    else -> "Zero"
    }

    ※ここでは主体を省略した when がより自然ですが、主体ありなら when (num) { 0 -> ..., in 1..Int.MAX_VALUE -> ..., in Int.MIN_VALUE..-1 -> ... } のようにも書けます。このシンプルなケースでは if 式の方が簡潔かもしれません。

例2: 点数による評価

  • if 式:

    kotlin
    val score = 75
    val grade = if (score >= 90) "A" else if (score >= 80) "B" else if (score >= 70) "C" else "D"

  • when 式(主体あり、範囲を使用):

    kotlin
    val score = 75
    val grade = when (score) {
    in 90..100 -> "A"
    in 80..89 -> "B"
    in 70..79 -> "C"
    else -> "D"
    }

    このケースでは、when 式が範囲指定を活用できるため、非常に読みやすく効率的です。

例3: オブジェクトのタイプによる処理

  • if 式(is とスマートキャストを使用):

    kotlin
    fun handle(item: Any) {
    if (item is String) {
    println("Processing String: ${item.toUpperCase()}")
    } else if (item is Int) {
    println("Processing Int: ${item * 10}")
    } else {
    println("Processing unknown type")
    }
    }

  • when 式(is を使用):

    kotlin
    fun handle(item: Any) {
    when (item) {
    is String -> println("Processing String: ${item.toUpperCase()}")
    is Int -> println("Processing Int: ${item * 10}")
    else -> println("Processing unknown type")
    }
    }

    このケースでは、when 式の方が構造が明確で、複数の型に対する分岐を分かりやすく記述できます。

結論として、Kotlinでは三項演算子という専用の構文はありませんが、if 式と when 式がその役割を十分に果たし、さらにそれ以上の表現力と柔軟性を提供します。どちらを選ぶかは、条件の複雑さ、対象となる値の種類、分岐の数など、状況に応じて最適な可読性と簡潔さを提供する方を選ぶべきです。

実践的な例

実際の開発シナリオにおける if 式と when 式の活用例を見ていきましょう。

例1: Javaの三項演算子の移行

Javaの以下のコードをKotlinに移行します。

java
// Java
String status = (user.isActive() && user.hasProfile()) ? "Active" : "Inactive";

Kotlinでは if 式を使用します。

kotlin
// Kotlin
val status = if (user.isActive() && user.hasProfile()) "Active" else "Inactive"

このように、Javaの三項演算子はKotlinのシンプルな if-else 式に自然に置き換えられます。

例2: 数値の符号判定

“`kotlin
// 数値の符号を判定して文字列で返す
fun getSign(number: Int): String {
return if (number > 0) {
“Positive”
} else if (number < 0) {
“Negative”
} else {
“Zero”
}
}

println(getSign(10)) // Output: Positive
println(getSign(-5)) // Output: Negative
println(getSign(0)) // Output: Zero
``
この例は
if-else if-else` チェーンとして適切です。

例3: HTTPステータスコードの処理

HTTPステータスコードに応じてメッセージを返す関数を when 式で実装します。

“`kotlin
fun getHttpStatusMessage(code: Int): String {
return when (code) {
200 -> “OK”
201, 204 -> “Success (No Content)” // 複数の成功コード
in 300..399 -> “Redirection” // リダイレクト系
400 -> “Bad Request”
401 -> “Unauthorized”
403 -> “Forbidden”
404 -> “Not Found”
in 500..599 -> “Server Error” // サーバーエラー系
else -> “Unknown Status” // その他のコード
}
}

println(getHttpStatusMessage(200)) // Output: OK
println(getHttpStatusMessage(301)) // Output: Redirection
println(getHttpStatusMessage(404)) // Output: Not Found
println(getHttpStatusMessage(503)) // Output: Server Error
println(getHttpStatusMessage(100)) // Output: Unknown Status
``
このように、
when` 式は複数の値を指定したり、範囲を指定したりできるため、カテゴリ分けが必要な分岐に非常に適しています。

例4: ユーザーの種類に応じた処理

ユーザーオブジェクトの型やプロパティの値に基づいて異なる処理を行う場合。

“`kotlin
interface User
class RegisteredUser(val userId: String, val name: String) : User
class GuestUser(val sessionId: String) : User
object AnonymousUser : User // シングルトンオブジェクト

fun greetUser(user: User) {
when (user) {
is RegisteredUser -> println(“Welcome back, ${user.name} (ID: ${user.userId})!”)
is GuestUser -> println(“Hello, Guest user (Session: ${user.sessionId}). Please register.”)
AnonymousUser -> println(“Hello, Anonymous user. Please log in or register.”) // シングルトンオブジェクトとの一致
else -> println(“Hello, unknown user type.”) // 万が一の場合
}
}

val registered = RegisteredUser(“user123”, “Alice”)
val guest = GuestUser(“sess456”)
val anonymous = AnonymousUser

greetUser(registered) // Output: Welcome back, Alice (ID: user123)!
greetUser(guest) // Output: Hello, Guest user (Session: sess456). Please register.
greetUser(anonymous) // Output: Hello, Anonymous user. Please log in or register.

// Sealed class の場合 (前述のように else が不要になる可能性あり)
sealed class AuthenticationState
object Authenticated : AuthenticationState()
object Unauthenticated : AuthenticationState()
data class ErrorState(val message: String) : AuthenticationState()

fun displayAuthState(state: AuthenticationState) {
when (state) { // sealed class なので else 句は不要 (すべてのサブクラスを網羅しているため)
Authenticated -> println(“Authenticated: Access granted.”)
Unauthenticated -> println(“Unauthenticated: Please log in.”)
is ErrorState -> println(“Authentication Error: ${state.message}”)
}
}

displayAuthState(Authenticated) // Output: Authenticated: Access granted.
displayAuthState(ErrorState(“Invalid credentials”)) // Output: Authentication Error: Invalid credentials
``when式のis条件は、オブジェクト指向プログラミングにおけるポリモーフィズムと組み合わせることで、型の異なるオブジェクトに対する処理を簡潔かつ安全に記述できます。特にsealed classsealed interface` と組み合わせると、コンパイラによる網羅性チェックの恩恵を受けられます。

例5: 主体なし when による複雑な条件

複数の異なる条件を組み合わせて分岐する場合、主体なしの when が役立ちます。

“`kotlin
val isLoggedIn = true
val isAdmin = false
val featureEnabled = true

val action = when {
!isLoggedIn -> “Please log in to perform this action.” // ログインしていない場合
isAdmin && featureEnabled -> “Admin action allowed.” // 管理者かつ機能が有効な場合
isLoggedIn && featureEnabled -> “User action allowed.” // ログイン済みかつ機能が有効な場合 (isAdmin は偽)
isLoggedIn && !featureEnabled -> “Feature is disabled.” // ログイン済みだが機能が無効な場合
else -> “Unknown state.” // 万が一のフォールバック (実際には上記の条件で全て網羅されている可能性が高い)
}

println(action) // Output: User action allowed. (この例の isLoggedIn=true, isAdmin=false, featureEnabled=true の場合)

val isLoggedInAdminEnabled = true
val isAdminAdminEnabled = true
val featureEnabledAdminEnabled = true

val actionAdmin = when {
!isLoggedInAdminEnabled -> “Please log in to perform this action.”
isAdminAdminEnabled && featureEnabledAdminEnabled -> “Admin action allowed.”
isLoggedInAdminEnabled && featureEnabledAdminEnabled -> “User action allowed.” // この条件には到達しない (前の条件が先に真になるため)
isLoggedInAdminEnabled && !featureEnabledAdminEnabled -> “Feature is disabled.”
else -> “Unknown state.”
}

println(actionAdmin) // Output: Admin action allowed.
``
主体なし
whenは、条件が評価される順序に注意が必要です。最初の真になった条件に対応するブロックのみが実行されます。この例のように、より具体的な条件(isAdmin && featureEnabled)を先に記述することで、意図した通りの分岐を実現できます。これはif-else ifチェーンと同じロジックです。主体なしwhen` を使うことで、条件のリストが視覚的に整理され、複雑な条件分岐の全体像を把握しやすくなる場合があります。

高度なトピック / 関連機能

Kotlinの条件分岐に関連する、さらに高度なトピックや便利な機能についても触れておきます。

スコープ関数との組み合わせ

let, run, apply, also, with といったスコープ関数は、null許容型(?)の値を扱う際や、オブジェクトのコンテキスト内で処理を行う際に条件分岐と組み合わせて頻繁に使用されます。特に let は、nullでない場合にブロックを実行するという条件分岐によく使われます。

“`kotlin
val nullableValue: String? = “Hello”

// nullableValue が null でない場合のみ処理
nullableValue?.let { nonNullValue ->
// nonNullValue は String 型として扱える
println(“Value is not null: $nonNullValue”)
val length = nonNullValue.length // スマートキャストされている
println(“Length: $length”)
} ?: run {
// nullableValue が null の場合に実行 (Elvis演算子 ?: と run を使用)
println(“Value is null”)
}

// 上記は以下の if 式と同等
if (nullableValue != null) {
val nonNullValue = nullableValue // スマートキャストされている
println(“Value is not null: $nonNullValue”)
val length = nonNullValue.length
println(“Length: $length”)
} else {
println(“Value is null”)
}
``?.let { … } ?: run { … }というイディオムは、null許容値に対するif-else` 式の非常にKotlinらしい代替手段となります。

前提条件チェック関数 (require, check, assert)

関数やプロパティの呼び出し元に特定の前提条件(Preconditions)を満たすことを要求したい場合、Kotlin標準ライブラリの require, check, assert 関数が便利です。これらは条件が満たされない場合に例外をスローします。

  • require(condition: Boolean, lazyMessage: () -> Any): 関数の引数など、呼び出し元が不正な値を渡した場合に IllegalArgumentException をスローします。条件が満たされないのは、呼び出し元のプログラマのミスである可能性が高い場合に使います。
  • check(condition: Boolean, lazyMessage: () -> Any): オブジェクトの状態など、実行時の想定外の状態が発生した場合に IllegalStateException をスローします。条件が満たされないのは、内部的なロジックのエラーや、外部要因による状態変化である可能性が高い場合に使います。
  • assert(condition: Boolean, lazyMessage: () -> Any): 開発中のデバッグ目的で使用します。テストやデバッグビルドでのみ有効になり、本番環境では通常無効化されます(JVMでは -ea オプションで有効化)。条件が満たされない場合に AssertionError をスローします。

これらの関数は、条件分岐を使って手動で例外をスローするよりも、意図が明確になり、コードが簡潔になります。

“`kotlin
fun processPositiveNumber(number: Int) {
require(number > 0) { “Input number must be positive, but was $number” }
// number が正であることが保証される
println(“Processing positive number: $number”)
}

// processPositiveNumber(-5) // この呼び出しは IllegalArgumentException をスローする
processPositiveNumber(10) // 正常に実行される
“`

パターンマッチングとKotlinの when

他の言語(Scala, Rust, Haskellなど)にある「パターンマッチング」は、値の構造を分解しながら条件分岐を行う強力な機能です。Kotlinの when 式の is 条件や sealed class との組み合わせは、このパターンマッチングの一部の機能(判別共用体に対する網羅的な分岐)を提供しますが、完全なパターンマッチング(値の分解などを含む)ではありません。

例えば、Scalaでは以下のようにデータ構造を分解しながらマッチングできます。

“`scala
// Scala の例
case class Person(name: String, age: Int)
case object Unknown

def describe(obj: Any): String = obj match {
case Person(name, age) if age > 18 => s”$name (Adult)” // 構造を分解し、ageが18より大きいかをチェック
case Person(name, _) => s”$name (Minor)” // 構造を分解し、ageは気にしない
case s: String => s”String: $s”
case i: Int => s”Int: $i”
case Unknown => “Unknown object”
case _ => “Something else” // デフォルトケース
}

println(describe(Person(“Alice”, 30))) // 出力: Alice (Adult)
println(describe(Person(“Bob”, 16))) // 出力: Bob (Minor)
“`

Kotlinの when 式は、is 条件による型チェックとスマートキャストを組み合わせることで、これに近いことを実現できますが、構造の分解は別途行う必要があります。

“`kotlin
// Kotlin で同等のことをする場合
data class Person(val name: String, val age: Int)
object Unknown

fun describe(obj: Any): String {
return when (obj) {
is Person -> {
if (obj.age > 18) { // 分解したプロパティに対する追加条件
“${obj.name} (Adult)”
} else {
“${obj.name} (Minor)”
}
}
is String -> “String: $obj”
is Int -> “Int: $obj”
Unknown -> “Unknown object”
else -> “Something else”
}
}

println(describe(Person(“Alice”, 30))) // 出力: Alice (Adult)
println(describe(Person(“Bob”, 16))) // 出力: Bob (Minor)
``
Kotlinの
whenは、完全なパターンマッチングではないものの、多くのシナリオで十分な表現力と安全性を提供します。特にsealed class/interface` との組み合わせは、代数的データ型(Algebraic Data Types)を使ったパターンマッチングの重要なユースケースをカバーしており、網羅的な条件分岐を安全に実現するための強力な機能です。

まとめ

この記事では、KotlinにはJavaやC++のような専用の「三項演算子」構文が存在しない理由と、その代替となる if 式および when 式について、それぞれの詳細な機能、利点、欠点、そして適切な使い分けについて解説しました。

Kotlinに三項演算子がないのは、if がステートメントではなく「式」として設計されており、評価結果として値を返すことができるためです。これにより、val result = if (condition) value_if_true else value_if_false という構文で、他の言語の三項演算子と同等以上の簡潔さと可読性を実現しています。

  • ifは、単純な真偽判定や少数の else if による分岐に最適です。式として値を返すことができるため、簡潔に変数への代入などに利用できます。
  • whenは、単一の主体に対する多数の分岐、値の範囲チェック、型のチェックなど、より複雑で多様な条件分岐に非常に強力です。こちらも式として値を返すことができ、特に sealed classsealed interface と組み合わせた場合の網羅性チェックは、安全なコード記述に貢献します。主体なしの when は、複雑な if-else if チェーンをより構造的に記述する代替手段となります。

どちらを使うべきかは、条件分岐の性質によって異なります。シンプルなケースでは if を、値の列挙や範囲、型による分岐が多いケースでは when を選ぶのが一般的です。主体なし when は、複数の変数に依存する複雑な条件分岐を整理したい場合に有効です。

Kotlinの if 式と when 式は、単なる条件分岐の手段にとどまらず、値を返す「式」であるという特性を通じて、より関数型に近いスタイルでのプログラミングを可能にし、コードの簡潔さ、可読性、安全性を向上させます。

これらの強力な条件分岐構文を適切に使いこなすことで、Kotlinらしい、より効率的でメンテナンスしやすいコードを書くことができるようになるでしょう。Javaからの移行を検討している方にとっても、Kotlinの if 式や when 式は、Javaの三項演算子や switch ステートメント、さらには長い if-else if チェーンに対する、強力かつ魅力的な代替手段となるはずです。

ぜひ、これらの構文を積極的に活用して、Kotlinでの開発を楽しんでください。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール