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)  // 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)

대괄호는 적절한 수의 인수를 가진 getset 호출로 변환됩니다.

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이며, null이 아닌 x에 대한 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 호출로 변환됩니다.

프로퍼티 위임 연산자

provideDelegate, getValue, setValue 연산자 함수는 위임된 프로퍼티에서 설명합니다.

이름 있는 함수에 대한 중위 호출

중위 함수 호출을 사용하여 사용자 지정 중위 연산을 시뮬레이션할 수 있습니다.