はじめに
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
はすでに component1
と component2
を持っていますが、
拡張してキー・値を加工することも可能です。
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 の活用
分解宣言と相性がいいのが Pair
と Triple
。
関数の結果を自然な形で扱えます。
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 | 柔軟な表現力とコードの美しさ |