はじめに
Kotlin では、
+
, -
, *
, []
, in
などの演算子を
自作クラスに自然に使えるようにできます。
これが「operator overloading(演算子のオーバーロード)」です。
1. 基本構文
Kotlin では、
特定の関数名に operator
修飾子をつけることで、
演算子として呼び出せるようになります。
data class Vec(val x: Int, val y: Int) {
operator fun plus(other: Vec) = Vec(x + other.x, y + other.y)
}
これで次のように書けます。
val a = Vec(1, 2)
val b = Vec(3, 4)
val c = a + b // ← operator fun plus が呼ばれる
println(c) // Vec(x=4, y=6)
内部的には a.plus(b)
と同じ意味です。
2. 対応できる operator 一覧(代表的なもの)
演算子 | 対応関数名 | 例 |
---|---|---|
+ |
plus |
a + b |
- |
minus |
a - b |
* |
times |
a * b |
/ |
div |
a / b |
% |
rem |
a % b |
++ / --
|
inc / dec
|
a++ , --a
|
[] |
get / set
|
a[i] , a[i] = v
|
in |
contains |
x in list |
== / !=
|
equals |
a == b |
> / < / >= / <=
|
compareTo |
a > b |
() |
invoke |
a() |
.. |
rangeTo |
1..5 |
+= / -=
|
plusAssign / minusAssign
|
list += x |
3. 加算・減算などの例
data class Vec(val x: Int, val y: Int) {
operator fun plus(other: Vec) = Vec(x + other.x, y + other.y)
operator fun minus(other: Vec) = Vec(x - other.x, y - other.y)
}
fun main() {
val a = Vec(5, 10)
val b = Vec(2, 3)
println(a + b) // Vec(x=7, y=13)
println(a - b) // Vec(x=3, y=7)
}
4. インクリメント・デクリメント
data class Counter(var value: Int) {
operator fun inc() = Counter(value + 1)
operator fun dec() = Counter(value - 1)
}
fun main() {
var c = Counter(10)
println(++c) // Counter(value=11)
println(c--) // Counter(value=11)(postfix)
println(c) // Counter(value=10)
}
5. 比較演算子 compareTo
<
, >
, <=
, >=
を有効にするには compareTo
を定義します。
data class Version(val major: Int, val minor: Int): Comparable<Version> {
override operator fun compareTo(other: Version): Int {
return if (major != other.major)
major - other.major
else
minor - other.minor
}
}
fun main() {
val v1 = Version(1, 2)
val v2 = Version(1, 5)
println(v1 < v2) // true
}
Kotlinでは内部的に v1.compareTo(v2) < 0
と評価されます。
6. get
/ set
で添字アクセス
class Matrix {
private val data = Array(3) { IntArray(3) }
operator fun get(i: Int, j: Int) = data[i][j]
operator fun set(i: Int, j: Int, value: Int) {
data[i][j] = value
}
}
fun main() {
val m = Matrix()
m[1, 2] = 42
println(m[1, 2]) // 42
}
a[i, j]
→ a.get(i, j)
a[i, j] = v
→ a.set(i, j, v)
7. in
と contains
data class User(val name: String)
class UserGroup(private val members: List<User>) {
operator fun contains(user: User): Boolean {
return members.any { it.name == user.name }
}
}
fun main() {
val group = UserGroup(listOf(User("Anna"), User("Bob")))
println(User("Anna") in group) // true
println(User("Eve") in group) // false
}
内部的には group.contains(user)
を呼び出しています。
8. invoke
演算子で関数のように呼び出す
class Greeter(val message: String) {
operator fun invoke(name: String) {
println("$message, $name!")
}
}
fun main() {
val hello = Greeter("Hello")
hello("Anna") // Hello, Anna!
}
hello.invoke("Anna")
の糖衣構文。
DSL風・関数オブジェクトの設計に最適。
9. plusAssign
/ minusAssign
で +=
に対応
class Bag<T> {
private val items = mutableListOf<T>()
operator fun plusAssign(item: T) {
items.add(item)
}
override fun toString() = items.toString()
}
fun main() {
val bag = Bag<String>()
bag += "Apple"
bag += "Banana"
println(bag) // [Apple, Banana]
}
+=
→ plusAssign()
-=
→ minusAssign()
10. rangeTo
と iterator
範囲表現(..
)や for
ループもカスタマイズ可能!
data class Vec(val x: Int, val y: Int)
data class VecRange(val start: Vec, val endInclusive: Vec)
operator fun Vec.rangeTo(other: Vec): VecRange = VecRange(this, other)
fun main() {
val a = Vec(1, 2)
val b = Vec(5, 6)
val range = a..b
println(range) // VecRange(start=Vec(x=1, y=2), endInclusive=Vec(x=5, y=6))
}
11. operator × DSL の一例(応用)
class Router {
private val routes = mutableMapOf<String, String>()
operator fun String.invoke(handler: String) {
routes[this] = handler
}
fun show() = println(routes)
}
fun main() {
val router = Router()
with(router) {
"/home"("HomeHandler")
"/user"("UserHandler")
}
router.show()
}
出力:
{/home=HomeHandler, /user=UserHandler}
これは簡易 DSL(ドメイン固有言語)としても使える例です。
まとめ
-
operator
は 演算子の挙動を関数で定義できる強力な仕組み -
data class
だけでなく、独自クラスでも自然な構文が書ける -
invoke
,get
,contains
などを使うと DSL・宣言的コード設計 にも応用可能 - Kotlin のシンタックスシュガーを 安全に制御できる のが最大の魅力