読み込み中...

Kotlinでのパターンマッチの方法12選

Kotlinのロゴと、サンプルコードと、パターンマッチという文字 Kotlin
この記事は約21分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

今回の記事では、プログラミング言語「Kotlin」の魅力的な機能の一つ、パターンマッチングについて解説していきます。

Kotlinを初めて学ぶ方でも理解できるように、基本から応用まで順序良く、分かりやすく説明していきます。

「パターンマッチングって何?」、「Kotlinでの使い方は?」という疑問から、より高度な応用例やカスタマイズ方法まで、この記事を読み終わるころには、あなたもKotlinのパターンマッチングを効果的に使用することができるようになるでしょう。

そして、コードの読みやすさや効率を向上させるためのヒントも得られるはずです。

●Kotlinのパターンマッチとは

パターンマッチングは、多くの関数型プログラミング言語に見られる機能で、Kotlinも例外ではありません。

パターンマッチングとは、あるデータが特定の「パターン」に一致するかどうかを判定し、一致する場合に特定の処理を行うというものです。

例えば、リストやタプル、データクラスのインスタンスなど、さまざまなデータに対してこのパターンマッチングを利用することができます。

Kotlinのwhen式と組み合わせることで、非常に読みやすく効率的なコードを書くことが可能となります。

○パターンマッチの基本

Kotlinのwhen式を使うと、値やオブジェクトの型に応じた処理を分岐させることができます。

これにより、if-else文を使用するよりもコードがスッキリとし、意図が明確に伝わるようになります。

特に、Kotlinのwhen式は他の多くのプログラミング言語のswitch文とは異なり、様々な型や条件でのマッチングが可能です。

これにより、複雑な条件分岐もシンプルに書き下すことができます。

●パターンマッチの使い方

Kotlinでのパターンマッチは、コードの可読性を向上させ、複雑な条件分岐をシンプルに表現するための強力なツールです。

ここでは、いくつかの一般的なシナリオにおいてパターンマッチを使ったコードの書き方をご紹介します。

○サンプルコード1:基本的なパターンマッチ

この例では、when式を使って整数の値に応じたメッセージを出力する基本的なパターンマッチを紹介します。

このコードでは、変数numberの値に応じて、異なる文字列を出力します。

fun main() {
    val number = 5  // この値を変更して結果を確かめてみてください。
    val message = when (number) {
        1 -> "one"
        2 -> "two"
        3 -> "three"
        else -> "unknown"  // 1, 2, 3以外の場合の処理
    }
    println("Number is: $message")
}

このコードでは、when式を使用して、number変数の値が1、2、3の場合にそれぞれ”one”、”two”、”three”という文字列を返しています。

それ以外の値の場合は”unknown”を返します。

このコードを実行すると、numberが5であるため、”unknown”と出力されます。

○サンプルコード2:リストの中の要素をパターンマッチ

このサンプルコードでは、リストの中の要素に対してパターンマッチを行い、特定の要素が存在するかをチェックします。

fun main() {
    val fruits = listOf("apple", "banana", "cherry")  // フルーツのリスト

    // リスト内の要素に対するパターンマッチ
    val message = when {
        "apple" in fruits -> "There is an apple."
        "grape" in fruits -> "There is a grape."
        else -> "Not found."
    }
    println(message)
}

このコード例では、フルーツの名前を含むリストfruitsを定義しています。

when式の中でinキーワードを使用して、特定のフルーツがリスト内に存在するかをチェックしています。

“apple”がリスト内に存在する場合、”There is an apple.”と出力し、”grape”が存在する場合は”There is a grape.”と出力します。

どちらも存在しない場合は、”Not found.”と出力されます。

このコードを実行すると、”apple”がfruitsリストに含まれているので、”There is an apple.”と表示されます。

○サンプルコード3:データクラスのプロパティをマッチ

データクラスは、Kotlinでのデータのモデリングに非常に便利です

データクラスのプロパティに基づいてパターンマッチを行う場合もあります。

この方法を使用すると、オブジェクトの特定のプロパティが特定の条件に一致するかどうかを簡単に確認できます。

ここでは、Personというデータクラスを作成し、そのプロパティを基にパターンマッチングを行う例を紹介します。

data class Person(val name: String, val age: Int)

fun main() {
    val person = Person("Taro", 25)

    val result = when {
        person.name == "Taro" && person.age == 25 -> "Taroさん、25歳ですね。"
        person.name == "Jiro" -> "Jiroさんですね。"
        else -> "該当する情報はありません。"
    }
    println(result)
}

このコードでは、nameプロパティが”Taro”で、ageプロパティが25である場合、”Taroさん、25歳ですね。”と出力します。nameが”Jiro”である場合は、”Jiroさんですね。”と出力します。

それ以外の場合、”該当する情報はありません。”と出力します。

このコードを実行すると、”Taroさん、25歳ですね。”というメッセージが表示されます。

○サンプルコード4:パターンマッチでの条件分岐

when式は、多くの場面での条件分岐に利用できます。

複数の条件を持つ場合、when式を使用することで、条件分岐をより明確に、また簡潔に記述することができます。

下記の例は、整数の値に基づくシンプルな条件分岐を表しています。

fun main() {
    val number = 10
    val result = when {
        number < 5 -> "5より小さい"
        number in 5..15 -> "5から15の間"
        else -> "15より大きい"
    }
    println(result)
}

このコードを実行すると、”5から15の間”という結果が得られます。

○サンプルコード5:when式での複数条件マッチ

when式は複数の条件を一つの式の中で扱うことができるため、非常に柔軟です。

この柔軟性を活かして、一つのwhen式の中で複数の条件をチェックすることも可能です。

下記のコードは、文字列の長さと内容を同時にチェックしています。

fun main() {
    val input = "Kotlin"
    val result = when {
        input.length > 5 && input.contains("Kot") -> "長さが5以上で、'Kot'を含む"
        input.length <= 5 -> "長さが5以下"
        else -> "その他の文字列"
    }
    println(result)
}

このコードを実行すると、”長さが5以上で、’Kot’を含む”という結果が得られます。

●パターンマッチの応用例

Kotlinでのパターンマッチは、基本的な使用方法から応用的な手法まで多岐にわたります。

実際の開発の中で遭遇するさまざまなシチュエーションに合わせて、効果的にパターンマッチを利用することができます。

○サンプルコード6:独自の型でのマッチング

独自に定義した型に対してもパターンマッチを適用することが可能です。

ここでは、独自のShape型に対してパターンマッチを行う例を紹介します。

sealed class Shape {
    class Circle(val radius: Double) : Shape()
    class Square(val side: Double) : Shape()
}

fun computeArea(shape: Shape): Double = when(shape) {
    is Shape.Circle -> 3.14 * shape.radius * shape.radius
    is Shape.Square -> shape.side * shape.side
}

fun main() {
    val circle = Shape.Circle(5.0)
    val square = Shape.Square(4.0)
    println("Circleの面積は ${computeArea(circle)}")
    println("Squareの面積は ${computeArea(square)}")
}

このコードでは、Circleの面積は78.5、Squareの面積は16.0として計算され、それぞれが出力されます。

○サンプルコード7:ラムダ式を用いたパターンマッチ

Kotlinでは、ラムダ式とパターンマッチを組み合わせることで、より柔軟なマッチングを実現できます。

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5)
    val evenOrOdd = numbers.map {
        when {
            it % 2 == 0 -> "偶数"
            else -> "奇数"
        }
    }
    println(evenOrOdd) // [奇数, 偶数, 奇数, 偶数, 奇数]
}

上記のコードは、与えられた数字が偶数か奇数かを判定し、それぞれの結果をリストとして出力します。

この例のように、ラムダ式の中でwhen式を使用することで、シンプルで直感的なコードを書くことができます。

○サンプルコード8:シールドクラスを用いた網羅的なマッチ

Kotlinにはシールドクラスという特徴があり、このクラスは制限されたサブクラスのみを持つことができるため、パターンマッチの際にすべてのケースを網羅的にマッチングすることが可能です。

コンパイラはこの全ケースの網羅をチェックしてくれるので、何かを見落とすリスクが低くなります。

sealed class TrafficLight {
    object Red : TrafficLight()
    object Yellow : TrafficLight()
    object Green : TrafficLight()
}

fun lightAction(light: TrafficLight) = when(light) {
    TrafficLight.Red -> "停止"
    TrafficLight.Yellow -> "注意"
    TrafficLight.Green -> "進行"
}

fun main() {
    val lights = listOf(TrafficLight.Red, TrafficLight.Yellow, TrafficLight.Green)
    lights.forEach {
        println("${it::class.simpleName}のときのアクションは「${lightAction(it)}」です")
    }
}

上記のサンプルコードでは、交通信号の色ごとのアクションを定義しています。

main関数を実行すると、各信号の色に対応したアクションを出力します。

具体的には、「Redのときのアクションは「停止」です」「Yellowのときのアクションは「注意」です」「Greenのときのアクションは「進行」です」という内容が表示されることになります。

○サンプルコード9:パターンマッチと拡張関数の組み合わせ

Kotlinの強力な特徴の一つである拡張関数とパターンマッチを組み合わせることで、既存の型に新しい動作を追加しながら、簡潔にマッチングを行うことができます。

下記のサンプルは、Int型に対して偶数か奇数かを判定する拡張関数を追加し、それを利用してパターンマッチを行っています。

fun Int.isEven(): Boolean = this % 2 == 0

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5)
    val results = numbers.map {
        when {
            it.isEven() -> "偶数:$it"
            else -> "奇数:$it"
        }
    }
    results.forEach { println(it) }
}

このコードを実行すると、数字が偶数である場合は「偶数:〇〇」と、奇数である場合は「奇数:〇〇」という形式で各数字が出力されます。

この方法を取り入れることで、コードの可読性を向上させることが可能です。

○サンプルコード10:リストの内包表記とパターンマッチ

Kotlinでは、リストの内包表記を使用して、リスト内の要素を簡潔に操作することができます。

特に、パターンマッチと組み合わせることで、リストの要素に対して条件分岐を行いながら新しいリストを生成することが可能となります。

ここでは、リストの内包表記とパターンマッチを組み合わせた例を紹介します。

このコードでは、整数のリストから偶数のみを取り出して、その二倍の値を持つ新しいリストを生成します。

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

    // パターンマッチと内包表記を組み合わせる
    val doubledEvens = numbers.filter { it % 2 == 0 }.map { it * 2 }

    // 結果を出力
    println(doubledEvens)
}

このコードでは、まずnumbersリストからfilter関数を使用して偶数のみを取り出しています。

その後、map関数を使用して、それらの偶数を二倍にして新しいリストを生成しています。

このコードを実行すると、[4, 8, 12, 16, 20]という結果が得られることになります。

さらに、内包表記を使って条件分岐を行う場合も考えられます。

下記の例は、偶数は二倍にし、奇数はそのままの値を持つ新しいリストを生成するものです。

val adjustedNumbers = numbers.map {
    when {
        it % 2 == 0 -> it * 2
        else -> it
    }
}

// 結果を出力
println(adjustedNumbers)

このコードを実行すると、[1, 4, 3, 8, 5, 12, 7, 16, 9, 20]という結果が得られます。

このように、Kotlinのリストの内包表記とパターンマッチを組み合わせることで、簡潔にかつ効率的にリストの要素を操作することができます。

特に大きなデータの処理や変換を行う際には、このような技術の組み合わせが非常に有効となるでしょう。

○サンプルコード11:null安全なパターンマッチング

Kotlinは、null安全を強く意識した言語であり、nullを取り扱う際のリスクを大幅に削減するための工夫がたくさん施されています。

パターンマッチングでも、このnull安全な特性を活かすことができます。

ここでは、null許容の文字列リストからnullを取り除き、大文字に変換する例を紹介します。

val words: List<String?> = listOf("apple", null, "banana", "cherry", null)

val nonNullUppercasedWords = words.filterNotNull().map { it.uppercase() }

// 結果を出力
println(nonNullUppercasedWords)

このコードの中で、filterNotNull関数を使ってnullを取り除く操作を行っています。

その後、map関数を使用して大文字に変換しています。

このコードを実行すると、[APPLE, BANANA, CHERRY]という結果が出力されます。

このように、Kotlinではnull安全を考慮した多くの関数や機能が提供されており、これをパターンマッチングと組み合わせることで、安全かつ効率的なコードを実現することができます。

○サンプルコード12:パターンマッチを用いた再帰処理

再帰処理は、関数が自分自身を呼び出す処理のことを指し、特定の条件下での繰り返し処理を効率的に実現するための手法としてよく用いられます。

パターンマッチングと組み合わせることで、再帰処理をより直感的かつ安全に実装することが可能です。

ここでは、リストの要素を再帰的に合計する例を紹介します。

fun sumList(numbers: List<Int>): Int {
    return when {
        numbers.isEmpty() -> 0
        else -> numbers.first() + sumList(numbers.drop(1))
    }
}

fun main() {
    val result = sumList(listOf(1, 2, 3, 4, 5))
    println("リストの合計は

$result です")
}

このコードでは、sumList関数が自分自身を呼び出しています。

リストが空である場合には0を返し、それ以外の場合にはリストの最初の要素と、残りの要素の合計を足し合わせる処理を行っています。

このコードを実行すると、リストの合計値である15が出力されます。

パターンマッチングを使うことで、再帰処理の終了条件を明確にするとともに、コードの読みやすさも向上します。

特に複雑な再帰処理を実装する際には、このような組み合わせの技術が大変役立つことでしょう。

●注意点と対処法

Kotlinでのパターンマッチングを使う際、非常に便利で強力なツールではありますが、知っておくべき注意点や潜在的な落とし穴も存在します。

正しく使うために、これらの点を理解しておくことは非常に重要です。

○潜在的な落とし穴とその回避方法

□網羅性の欠如

パターンマッチングを使用する際、すべての可能性を網羅していないと、意図しない動作やエラーが発生するリスクがあります。

例えば、次のようなコードを考えます。

fun describe(obj: Any): String {
    return when(obj) {
        1 -> "One"
        "Hello" -> "Greeting"
        is Long -> "Long"
        else -> "Unknown"
    }
}

このコードでは、IntStringLong型に対するパターンマッチングを行っていますが、DoubleListなど他の型に対してのマッチングが考慮されていません。

そのため、それらの型をこの関数に渡すと”Unknown”が返されます。

必要に応じて、追加のパターンを加えることで網羅性を高めることができます。

□順序の問題

when式の中で、上から順にマッチングの評価が行われます。

そのため、具体的な条件を上に、抽象的な条件を下に配置することが重要です。

fun checkNumber(num: Int): String {
    return when(num) {
        in 1..10 -> "Between 1 to 10"
        is Int -> "It's an Integer"
        else -> "Other"
    }
}

このコードでは、is Intの条件が常にtrueとなるため、2番目の条件が実行されることはありません。

正しい順序で条件を配置することで、このような問題を回避できます。

○パターンマッチのパフォーマンスに関する考慮点

パターンマッチングは、多くの場合で効率的ですが、特定のシナリオではパフォーマンス上の問題が生じる可能性があります。

□大量の条件分岐

大量の条件分岐を持つwhen式は、最悪の場合、すべての条件を評価する必要があります。

これは、大量の条件分岐がある場合にパフォーマンスのボトルネックになる可能性があります。

fun matchLargeConditions(input: String): String {
    return when(input) {
        "case1" -> "result1"
        // ... 中略 ...
        "case1000" -> "result1000"
        else -> "Not Matched"
    }
}

このような場合、効率的なデータ構造や、他のアプローチを検討することが推奨されます。

□複雑なマッチング条件

高度なマッチング条件、特に正規表現を用いたマッチングは、パフォーマンスに影響を及ぼす可能性があります。

fun matchWithRegex(input: String): String {
    return when {
        input.matches(Regex("複雑な正規表現")) -> "Matched"
        else -> "Not Matched"
    }
}

正規表現のパフォーマンスへの影響を最小限に抑えるために、必要に応じて正規表現の最適化を行うことが重要です。

●カスタマイズ方法

Kotlinのパターンマッチングは、そのままでも非常に便利ですが、さらにカスタマイズを施すことで、より柔軟で強力なマッチングを実現することができます。

ここでは、Kotlinのパターンマッチングをカスタマイズする方法を2つ、具体的なサンプルコードと共にご紹介します。

○独自のマッチャーの作成

Kotlinでは、独自のマッチャーを作成することが可能です。

これにより、特定の条件に合致するかどうかを簡単に判定することができます。

具体的な例として、文字列が特定のパターンに合致するかをチェックするマッチャーを考えてみましょう。

fun String.isPatternMatched(pattern: String): Boolean {
    return this.contains(pattern)
}

fun main() {
    val str = "Kotlinのパターンマッチは素晴らしい"
    when {
        str.isPatternMatched("Kotlin") -> println("Kotlinに関する文章です。")
        str.isPatternMatched("Java") -> println("Javaに関する文章です。")
        else -> println("関連のない文章です。")
    }
}

このコードでは、文字列が特定のパターンに合致するかを判定するisPatternMatched関数を作成しています。

この関数を使うことで、when式の中で簡潔に文字列のパターンマッチングを行うことができます。

このコードを実行すると、”Kotlinに関する文章です。”と出力されます。

○既存ライブラリとの組み合わせ

Kotlinは標準ライブラリだけでなく、多くのサードパーティライブラリとも簡単に組み合わせることができます。

これにより、パターンマッチングの可能性をさらに広げることができます。

例えば、Arrowというライブラリは、Kotlinでの関数型プログラミングを強化するための多くのツールを提供しています。

Arrowの一部であるOption型を使って、パターンマッチングをさらに効果的に行う方法を見てみましょう。

import arrow.core.Option
import arrow.core.Some
import arrow.core.none

fun findValue(key: String): Option<String> {
    return when (key) {
        "Kotlin" -> Some("パターンマッチ")
        else -> none()
    }
}

fun main() {
    val key = "Kotlin"
    val result = findValue(key)
    when(result) {
        is Some -> println("キー${key}の値は${result.t}です。")
        else -> println("キー${key}の値は見つかりませんでした。")
    }
}

このコードでは、findValue関数がOption型を返すようにしています。

Option型は、値が存在する場合としない場合を安全に扱うための型です。

この型を用いることで、nullを返すことなく、値の有無を表現することができます。

このコードを実行すると、”キーKotlinの値はパターンマッチです。”と出力されます。

まとめ

Kotlinのパターンマッチングは、コードの可読性を高め、より簡潔に様々な条件を処理する力を持っています。

基本的なパターンマッチングから応用例、そしてカスタマイズ方法に至るまで、多彩な方法でこの機能を活用することができます。

本記事では、初心者向けにKotlinのパターンマッチングの基本から、実践的なサンプルコードを交えた応用例、さらにはパフォーマンスやカスタマイズに関する注意点を詳しく解説しました。

これらの知識をもとに、日々の開発でのコードの品質向上や、効率的な開発を実現するためのヒントを得ることができるでしょう。

Kotlinは日々進化を続ける言語であり、今後もさらなる新機能や改善が期待されます。

パターンマッチングのような強力な機能を活用し、Kotlinの世界をさらに深く探求してみてください。

最後に、Kotlinでの開発が、あなたのプログラミングライフをより豊かに、そして効果的にしてくれることを願っています。