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となります。

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

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

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が呼び出されたオブジェクト自体を変更(mutate)すべきではありません。

コンパイラは、後置形式(例:a++)の演算子の解決のために以下の手順を実行します:

  • aの型を決定します。これをTとします。
  • Tのレシーバーに適用可能な、operator修飾子を持ち引数のない関数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)

角括弧(Square brackets)は、適切な数の引数を持つ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)

丸括弧(Parentheses)は、適切な数の引数を持つ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において、代入は式(expression)ではありません

等価および不等価演算子

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

これらの演算子は、equals(other: Any?): Boolean 関数でのみ機能します。この関数をオーバーライドすることで、カスタムの等価性チェックの実装を提供できます。 同じ名前を持つ他の関数(例:equals(other: Foo))は無視されます。

Kotlinは、==式において両方のオペランドが直接nullと比較されておらず、かつ比較が2つの浮動小数点型間ではない場合に、.equals()を呼び出します。 それ以外の場合、Kotlinは直接的なnull比較には===を使用し、非nullの浮動小数点値については数値として比較します。

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

比較演算子

変換先
a > ba.compareTo(b) > 0
a < ba.compareTo(b) < 0
a >= ba.compareTo(b) >= 0
a <= ba.compareTo(b) <= 0

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

プロパティ委譲演算子

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

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

中置記法(infix notation)の関数呼び出しを使用することで、カスタムの中置操作を模倣することができます。