KotlinのOperatorについて

採用はこちら

Kotlinにおける「Operator」は、次の2つの視点で理解すると整理しやすくなります。

  1. 言語に組み込まれている演算子(構文)
  2. operatorキーワードによる演算子オーバーロード(構文糖衣)

Kotlinの演算子は単なる記号ではなく、すべて対応する関数呼び出しに変換される構文です。

ここがJavaとの大きな違いです。

目次

Kotlinの基本演算子

算術演算子

演算子展開される関数
a + ba.plus(b)
a - ba.minus(b)
a * ba.times(b)
a / ba.div(b)
a % ba.rem(b)
val a = 10
val b = 3
println(a + b) // 13

※ これは実質 a.plus(b) の呼び出しです。

代入系演算子

演算子展開
a += ba = a.plus(b) または a.plusAssign(b)
a -= ba.minusAssign(b)
a *= ba.timesAssign(b)

Kotlinでは、plusAssign が存在すればそれが優先され、存在しなければ a = a + b に変換されます。

比較演算子

==(構造的等価)

a == b は単純な equals() 呼び出しではありません。

実際には概ね次のように展開されます。

a?.equals(b) ?: (b === null)

つまり、

  • null安全
  • equalsを呼ぶ
  • 両方nullならtrue

という仕様です。

===(参照同一性)

a === b

これは参照が同一かどうかを判定します。

オーバーロードは不可能です。

比較

演算子展開
a < ba.compareTo(b) < 0
a > ba.compareTo(b) > 0
a <= ba.compareTo(b) <= 0
a >= ba.compareTo(b) >= 0
data class Score(val value: Int) : Comparable<Score> {
    override operator fun compareTo(other: Score): Int {
        return value - other.value
    }
}

範囲演算子

演算子展開
a..ba.rangeTo(b)
for (i in 1..5) { }

in / !in

x in y

y.contains(x)

に展開されます。

添字演算子 []

a[i]

a.get(i)

に展開されます。

a[i] = value

a.set(i, value)

に展開されます。

正しい例

class MyCollection {
    private val list = mutableListOf<Int>()

    operator fun get(index: Int): Int = list[index]

    operator fun set(index: Int, value: Int) {
        require(index >= 0)
        while (list.size <= index) list.add(0)
        list[index] = value
    }
}

fun main() {
    val c = MyCollection()
    c[0] = 10
    println(c[0]) // 10
}

invoke演算子

a()

a.invoke()

に展開されます。

class Printer {
    operator fun invoke(msg: String) {
        println(msg)
    }
}

val p = Printer()
p("Hello")

iterator(forループ)

for (x in collection)

は内部的に

collection.iterator()

を呼びます。

そのIteratorは

  • hasNext()
  • next()

を持つ必要があります。

インクリメント / デクリメント

Kotlinの ++-- は特殊です。

a++ の展開(概念的)

  1. 一時変数に a を保存
  2. a = a.inc()
  3. 式の値は元の a

++a の展開

  1. a = a.inc()
  2. 式の値も更新後

重要なのは

  • inc() の戻り値が変数へ再代入される
  • 言語仕様として「必ず不変」とは定義していない
  • ただし実務では不変オブジェクト設計が一般的
data class Counter(val value: Int) {
    operator fun inc(): Counter {
        return Counter(value + 1)
    }
}

単項演算子

演算子関数
+aunaryPlus
-aunaryMinus
!anot

分解宣言(componentN)

val (x, y) = point

point.component1()
point.component2()

に展開されます。

data classは自動生成します。

委譲プロパティ演算子

val name by delegate

  • getValue
  • setValue

に展開されます。

高度なDSL設計で使われます。

Kotlin Operatorの本質

Kotlinの演算子はすべて

記号ではなく、対応する関数への変換ルール

です。

つまり、Kotlinの演算子設計は

  • 可読性のための構文糖衣
  • DSL構築を強く意識した設計
  • Javaと違い、限定的にオーバーロード可能

という思想に基づいています。

実務でのベストプラクティス

数学的概念には有効

  • ベクトル
  • マネー型
  • 複素数

DSL設計に非常に強い

Gradle Kotlin DSL
Jetpack Compose

乱用は可読性を落とす

演算子は「直感的に意味が明確な場合のみ」使うべきです。

まとめ

KotlinのOperatorは次の5層構造で理解できます。

  1. 基本演算子(算術・比較)
  2. null安全演算子(?:, ?., !!)
  3. 構文糖衣としての展開ルール
  4. operatorキーワードによるオーバーロード
  5. DSL応用(invoke, get, componentN など)

以上、KotlinのOperatorについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次