Skip to content

演算子オーバーロード

Kotlinでは、型に対する定義済み演算子のセットにカスタム実装を提供できます。これらの演算子には、定義済みの記号表現(+*など)と優先順位があります。演算子を実装するには、対応する型に対して特定の名前を持つメンバー関数または拡張関数を提供します。この型は、二項演算の場合は左辺の型となり、単項演算の場合は引数の型となります。

演算子をオーバーロードするには、対応する関数をoperator修飾子でマークします。

kotlin
interface IndexedContainer {
    operator fun get(index: Int)
}

演算子のオーバーロードをオーバーライドする場合、operatorを省略できます。

kotlin
class OrdersList: IndexedContainer {
    override fun get(index: Int) { /*...*/ }   
}

単項演算

単項前置演算子

翻訳先
+aa.unaryPlus()
-aa.unaryMinus()
!aa.not()

この表は、コンパイラが例えば式+aを処理する際に、以下のステップを実行することを示しています。

  • aの型を決定し、それをTとする。
  • operator修飾子を持ち、レシーバーTに対するパラメータを持たないunaryPlus()関数を検索する。これはメンバー関数または拡張関数を意味する。
  • その関数が存在しないか、曖昧な場合は、コンパイルエラーとなる。
  • その関数が存在し、戻り値の型がRである場合、式+aは型Rを持つ。

これらの演算は、他のすべての演算と同様に、基本型向けに最適化されており、それらに対する関数呼び出しのオーバーヘッドを発生させません。

例として、単項マイナス演算子をオーバーロードする方法を次に示します。

kotlin
data class Point(val x: Int, val y: Int)

operator fun Point.unaryMinus() = Point(-x, -y)

val point = Point(10, 20)

fun main() {
   println(-point)  // prints "Point(x=-10, y=-20)"
}

インクリメントとデクリメント

翻訳先
a++a.inc() + 以下を参照
a--a.dec() + 以下を参照

inc()およびdec()関数は値を返さなければならず、その値は++または--演算子が使用された変数に代入されます。これらの関数は、incまたはdecが呼び出されたオブジェクトをミューテーション(変更)すべきではありません。

コンパイラは、例えばa++のような後置形式の演算子の解決のために、以下のステップを実行します。

  • aの型を決定し、それをTとする。
  • operator修飾子を持ち、パラメータがなく、型Tのレシーバーに適用可能なinc()関数を検索する。
  • その関数の戻り値の型がTのサブタイプであることを確認する。

式を計算した結果は次のとおりです。

  • aの初期値を一時記憶域a0に保存する。
  • a0.inc()の結果をaに代入する。
  • a0を式の結果として返す。

a--についても、手順は完全に類似しています。

前置形式の++a--aについても、解決は同じ方法で行われ、結果は次のとおりです。

  • a.inc()の結果をaに代入する。
  • aの新しい値を式の結果として返す。

二項演算

算術演算子

翻訳先
a + ba.plus(b)
a - ba.minus(b)
a * ba.times(b)
a / ba.div(b)
a % ba.rem(b)
a..ba.rangeTo(b)
a..<ba.rangeUntil(b)

この表の演算子については、コンパイラは翻訳先列の式を解決するだけです。

以下は、指定された値で始まり、オーバーロードされた+演算子を使用してインクリメントできるCounterクラスの例です。

kotlin
data class Counter(val dayIndex: Int) {
    operator fun plus(increment: Int): Counter {
        return Counter(dayIndex + increment)
    }
}

in 演算子

翻訳先
a in bb.contains(a)
a !in b!b.contains(a)

in!inについては手順は同じですが、引数の順序が逆になります。

添字アクセス演算子

翻訳先
a[i]a.get(i)
a[i, j]a.get(i, j)
a[i_1, ..., i_n]a.get(i_1, ..., i_n)
a[i] = ba.set(i, b)
a[i, j] = ba.set(i, j, b)
a[i_1, ..., i_n] = ba.set(i_1, ..., i_n, b)

角括弧は、適切な数の引数を持つgetおよびsetへの呼び出しに翻訳されます。

invoke 演算子

翻訳先
a()a.invoke()
a(i)a.invoke(i)
a(i, j)a.invoke(i, j)
a(i_1, ..., i_n)a.invoke(i_1, ..., i_n)

丸括弧は、適切な数の引数を持つinvokeへの呼び出しに翻訳されます。

複合代入

翻訳先
a += ba.plusAssign(b)
a -= ba.minusAssign(b)
a *= ba.timesAssign(b)
a /= ba.divAssign(b)
a %= ba.remAssign(b)

例えばa += bのような代入演算の場合、コンパイラは以下のステップを実行します。

  • 右列の関数が利用可能な場合:
    • 対応する二項関数(plusAssign()の場合はplus())も利用可能で、aが可変変数であり、plusの戻り値の型がaの型のサブタイプである場合、エラー(曖昧性)を報告する。
    • その戻り値の型がUnitであることを確認し、そうでない場合はエラーを報告する。
    • a.plusAssign(b)のコードを生成する。
  • それ以外の場合、a = a + bのコードを生成しようとする(これには型チェックが含まれ、a + bの型がaのサブタイプでなければならない)。

Kotlinにおいて、代入は式ではありません

等値演算子と不等値演算子

翻訳先
a == ba?.equals(b) ?: (b === null)
a != b!(a?.equals(b) ?: (b === null))

これらの演算子は、カスタムの等値チェック実装を提供するためにオーバーライドできる関数equals(other: Any?): Booleanとのみ連携します。同じ名前の他の関数(equals(other: Foo)など)は呼び出されません。

===!==(同一性チェック)はオーバーロードできないため、それらに関する規約は存在しません。

==演算は特殊です。これはnullをスクリーニングする複雑な式に翻訳されます。 null == nullは常にtrueであり、非nullxに対するx == nullは常にfalseとなり、x.equals()を呼び出しません。

比較演算子

翻訳先
a > ba.compareTo(b) > 0
a < ba.compareTo(b) < 0
a >= ba.compareTo(b) >= 0
a <= ba.compareTo(b) <= 0

すべての比較はcompareToへの呼び出しに翻訳され、compareToIntを返す必要があります。

プロパティ委譲演算子

provideDelegategetValuesetValueの演算子関数については、プロパティ委譲で説明されています。

名前付き関数の中置呼び出し

中置記法を使用することで、カスタムの中置演算をシミュレートできます。