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とします。
  • レシーバTに対して、operator修飾子を持ち、パラメータのないunaryPlus()関数、つまりメンバ関数または拡張関数を探します。
  • その関数が存在しないか、曖昧である場合、コンパイルエラーとなります。
  • その関数が存在し、その戻り値の型がRである場合、式+aは型Rを持ちます。

NOTE

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

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

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)  // "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のサブタイプでなければなりません)。

NOTE

Kotlinでは、代入は式ではありません

等価および非等価演算子

変換先
a == ba?.equals(b) ?: (b === null)
a != b!(a?.equals(b) ?: (b === null))

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

NOTE

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

==演算は特殊です。これは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

すべての比較は、Intを返す必要があるcompareToへの呼び出しに変換されます。

プロパティ委譲演算子

provideDelegategetValue、およびsetValue演算子関数は、委譲プロパティで説明されています。

名前付き関数のinfix呼び出し

infix関数呼び出しを使用することで、カスタムのinfix演算をシミュレートできます。