演算子オーバーロード
Kotlinでは、型に対する定義済み演算子のセットにカスタム実装を提供できます。これらの演算子には、定義済みの記号表現(+
や*
など)と優先順位があります。演算子を実装するには、対応する型に対して特定の名前を持つメンバー関数または拡張関数を提供します。この型は、二項演算の場合は左辺の型となり、単項演算の場合は引数の型となります。
演算子をオーバーロードするには、対応する関数をoperator
修飾子でマークします。
interface IndexedContainer {
operator fun get(index: Int)
}
演算子のオーバーロードをオーバーライドする場合、operator
を省略できます。
class OrdersList: IndexedContainer {
override fun get(index: Int) { /*...*/ }
}
単項演算
単項前置演算子
式 | 翻訳先 |
---|---|
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
この表は、コンパイラが例えば式+a
を処理する際に、以下のステップを実行することを示しています。
a
の型を決定し、それをT
とする。operator
修飾子を持ち、レシーバーT
に対するパラメータを持たないunaryPlus()
関数を検索する。これはメンバー関数または拡張関数を意味する。- その関数が存在しないか、曖昧な場合は、コンパイルエラーとなる。
- その関数が存在し、戻り値の型が
R
である場合、式+a
は型R
を持つ。
これらの演算は、他のすべての演算と同様に、基本型向けに最適化されており、それらに対する関数呼び出しのオーバーヘッドを発生させません。
例として、単項マイナス演算子をオーバーロードする方法を次に示します。
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 + b | a.plus(b) |
a - b | a.minus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.rem(b) |
a..b | a.rangeTo(b) |
a..<b | a.rangeUntil(b) |
この表の演算子については、コンパイラは翻訳先列の式を解決するだけです。
以下は、指定された値で始まり、オーバーロードされた+
演算子を使用してインクリメントできるCounter
クラスの例です。
data class Counter(val dayIndex: Int) {
operator fun plus(increment: Int): Counter {
return Counter(dayIndex + increment)
}
}
in
演算子
式 | 翻訳先 |
---|---|
a in b | b.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] = b | a.set(i, b) |
a[i, j] = b | a.set(i, j, b) |
a[i_1, ..., i_n] = b | a.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 += b | a.plusAssign(b) |
a -= b | a.minusAssign(b) |
a *= b | a.timesAssign(b) |
a /= b | a.divAssign(b) |
a %= b | a.remAssign(b) |
例えばa += b
のような代入演算の場合、コンパイラは以下のステップを実行します。
- 右列の関数が利用可能な場合:
- 対応する二項関数(
plusAssign()
の場合はplus()
)も利用可能で、a
が可変変数であり、plus
の戻り値の型がa
の型のサブタイプである場合、エラー(曖昧性)を報告する。 - その戻り値の型が
Unit
であることを確認し、そうでない場合はエラーを報告する。 a.plusAssign(b)
のコードを生成する。
- 対応する二項関数(
- それ以外の場合、
a = a + b
のコードを生成しようとする(これには型チェックが含まれ、a + b
の型がa
のサブタイプでなければならない)。
Kotlinにおいて、代入は式ではありません。
等値演算子と不等値演算子
式 | 翻訳先 |
---|---|
a == b | a?.equals(b) ?: (b === null) |
a != b | !(a?.equals(b) ?: (b === null)) |
これらの演算子は、カスタムの等値チェック実装を提供するためにオーバーライドできる関数equals(other: Any?): Boolean
とのみ連携します。同じ名前の他の関数(equals(other: Foo)
など)は呼び出されません。
===
と!==
(同一性チェック)はオーバーロードできないため、それらに関する規約は存在しません。
==
演算は特殊です。これはnull
をスクリーニングする複雑な式に翻訳されます。 null == null
は常にtrue
であり、非null
のx
に対するx == null
は常にfalse
となり、x.equals()
を呼び出しません。
比較演算子
式 | 翻訳先 |
---|---|
a > b | a.compareTo(b) > 0 |
a < b | a.compareTo(b) < 0 |
a >= b | a.compareTo(b) >= 0 |
a <= b | a.compareTo(b) <= 0 |
すべての比較はcompareTo
への呼び出しに翻訳され、compareTo
はInt
を返す必要があります。
プロパティ委譲演算子
provideDelegate
、getValue
、setValue
の演算子関数については、プロパティ委譲で説明されています。
名前付き関数の中置呼び出し
中置記法を使用することで、カスタムの中置演算をシミュレートできます。