0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Kotlin】Kotlin:分解宣言 × 拡張関数 × operator の組み合わせ活用

Posted at

はじめに

Kotlin の「分解宣言(Destructuring Declaration)」は、
単体でも便利ですが、拡張関数 (extension function)operator 関数 と組み合わせると、
驚くほど表現力のあるコードが書けます。

この記事では、
「分解宣言 × 拡張関数 × operator」の実践的な活用例を紹介します。


基本の復習:分解宣言とは?

まず基本を軽くおさらい。

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

val (name, age) = User("Anna", 28)
println("$name is $age years old")

これは内部的に以下のように展開されます:

val name = user.component1()
val age = user.component2()

つまり「componentN() 関数」を持つ型なら何でも分解できるというルールです。


応用1:既存クラスに分解を追加(拡張関数)

たとえば、標準ライブラリの LocalDateTime に分解機能を付けてみましょう。

import java.time.LocalDateTime

operator fun LocalDateTime.component1() = year
operator fun LocalDateTime.component2() = monthValue
operator fun LocalDateTime.component3() = dayOfMonth

fun main() {
    val (y, m, d) = LocalDateTime.now()
    println("$y/$m/$d")
}

LocalDateTime 自体は data class ではありませんが、
拡張関数+operator によって 分解宣言対応 にできます。


応用2:Map.Entry に独自の意味を与える

Map.Entry はすでに component1component2 を持っていますが、
拡張してキー・値を加工することも可能です。

operator fun <K, V> Map.Entry<K, V>.component3(): String {
    return "$key → $value"
}

fun main() {
    val map = mapOf("Tokyo" to "Japan", "Paris" to "France")
    for ((k, v, desc) in map) {
        println(desc)
    }
}

出力:

Tokyo → Japan
Paris → France

これで (key, value, desc) のように3要素分解が可能になります。


応用3:複雑なオブジェクトの部分抽出

data class にすべてのフィールドを含めたくない場合、
拡張で必要な分解だけを定義できます。

data class Address(val city: String, val country: String)
data class User(val name: String, val address: Address)

// 「ユーザー名」と「国」だけを分解したい
operator fun User.component1() = name
operator fun User.component2() = address.country

fun main() {
    val user = User("Anna", Address("Tokyo", "Japan"))
    val (name, country) = user
    println("$name lives in $country")
}

出力:

Anna lives in Japan

必要な情報だけを分解可能
データ構造を直接変更せずに柔軟な構文を追加できます。


応用4:拡張関数 + Pair / Triple の活用

分解宣言と相性がいいのが PairTriple
関数の結果を自然な形で扱えます。

fun String.splitName(): Pair<String, String> {
    val parts = split(" ")
    return parts[0] to parts[1]
}

fun main() {
    val (first, last) = "Anna Wang".splitName()
    println("First: $first, Last: $last")
}

結果:

First: Anna, Last: Wang

ここで Pair が自動的に component1() / component2() を持っているため、
分解宣言がそのまま使えます。


応用5:operator + 拡張で “DSL風” 構文に

Kotlin では operator と拡張を組み合わせることで、
小さなDSL(ドメイン固有言語)を作ることも可能です。

data class Vec(val x: Int, val y: Int)

operator fun Vec.component1() = x
operator fun Vec.component2() = y
operator fun Vec.plus(other: Vec) = Vec(x + other.x, y + other.y)

fun main() {
    val v1 = Vec(1, 2)
    val v2 = Vec(3, 4)
    val v3 = v1 + v2

    val (x, y) = v3
    println("Result = ($x, $y)")
}

出力:

Result = (4, 6)

componentN + plus によって
数値ベクトルのような直感的な演算が可能になります。


Bonus:List から「先頭+残り」を分解

Kotlin の List に直接拡張して「頭と尻」を分解することもできます:

operator fun <T> List<T>.component1() = first()
operator fun <T> List<T>.component2() = drop(1)

fun main() {
    val list = listOf(10, 20, 30)
    val (head, tail) = list
    println("head = $head, tail = $tail")
}

出力:

head = 10, tail = [20, 30]

これにより、再帰処理やパーサー実装が自然に書けます。


まとめ

要素 役割 ポイント
componentN() 分解宣言を支える関数 operator 修飾子が必要
拡張関数 既存クラスを拡張 data class 以外にも分解可能
operator 演算子オーバーロード +, -, [], in などと組み合わせ可能
応用例 DSL / Vec / LocalDateTime / Map.Entry 柔軟な表現力とコードの美しさ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?