문서비공식 한국어 번역
GitHub문서

연산자 오버로딩

 이 페이지의 마지막 수정: 2024년 8월 24일 
 ...

Kotlin 은 타입들에 미리 정의된 연산자들의 동작을 수정 구현할 수 있도록 허용합니다. 이러한 연산자들은 미리 정의된 기호 표현이나 우선순위를 가지고 있습니다. 연산자를 구현하려면, 연산하려는 타입에 특정한 이름을 가지는 멤버 함수확장 함수를 구현하면 됩니다. 그 타입은 이항 연산들에 대해서는 연산자 좌측의 값이 되며, 단항 연산자에 대해서는 연산하려는 주체가 됩니다.

연산자를 오버로드하려면, 해당하는 함수를 operator 수정자로 표기하세요:

1interface IndexedContainer {
2    operator fun get(index: Int)
3}
4

오퍼레이터 오버로드를 재정의할 때는, operator 수정자를 생략할 수 있습니다:

1class OrdersList: IndexedContainer {
2    override fun get(index: Int) { /*...*/ }
3}
4

단항 연산자

단항 전위 연산자

표현사용되는 함수
+aa.unaryPlus()
-aa.unaryMinus()
!aa.not()

이 표는 예를 들어 컴파일러가 +a 라는 표현을 처리할 때 아래와 같은 과정을 거친다는 것을 나타냅니다:

  • a 의 타입을 확인합니다. 여기에서는 T 라고 해보겠습니다.
  • operator 수정자를 가지면서 아무런 인수를 받지 않고, T 수신자를 가지는 unaryPlus() 멤버나 확장 함수를 찾습니다.
  • 그런 함수가 없거나, 어떤 것을 사용할지 모호한 상황이라면, 컴파일 에러가 보고됩니다.
  • 만약 함수가 존재하면서 리턴 타입이 R 이라면, +a 라는 표현의 타입은 R 이 됩니다.

이러한 연산들은 기본 타입 들에 대해서는 모두 최적화가 진행되며 함수 호출의 오버헤드가 발생하지 않습니다.

예를 들어, 단항 빼기 연산을 오버로드할 때는 아래 처럼 합니다:

1data class Point(val x: Int, val y: Int)
2
3operator fun Point.unaryMinus() = Point(-x, -y)
4
5val point = Point(10, 20)
6
7fun main() {
8   println(-point)  // prints "Point(x=-10, y=-20)"
9}
10

증감 연산

표현사용되는 함수
a++a.inc() + 아래쪽 설명을 확인하세요
a--a.dec() + 아래쪽 설명을 확인하세요

inc()dec() 함수는 반드시 이 연산이 행해진 변수에 새로이 대입될 값을 리턴해야합니다. 이 함수들은 호출된 자기 자신의 오브젝트를 변경하지 않는 것이 좋습니다.

컴파일러는 예를 들면 a++ 와 같은 후위 증감연산에 대해 아래와 같이 처리합니다:

  • a 의 타입을 확인합니다. 여기에서는 T 라고 해보겠습니다.
  • operator 수정자를 가지면서 아무런 인수를 받지 않고, T 수신자를 가지는 inc() 함수를 찾습니다.
  • 그 함수의 리턴 타입이 T 이거나 그의 서브타입인지 확인합니다.

표현의 계산은 아래처럼 이루어집니다.

  • a 의 초기값을 임시 공간 a0 에 저장합니다.
  • a0.inc() 의 값을 a 에 저장합니다.
  • a0 을 표현의 결과로 리턴합니다.

++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 라는 클래스에 대해 주어진 값으로부터 시작하여 오버로드된 + 연산자를 통해 그 값을 증가시킬 수 있는 예제입니다:

1data class Counter(val dayIndex: Int) {
2    operator fun plus(increment: Int): Counter {
3        return Counter(dayIndex + increment)
4    }
5}
6

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 함수로 변환됩니다.

호출 연산자

표현사용되는 함수
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 가 변경 가능한 변수이면서, 그 수학적 연산 함수의 결과가 a 의 서브타입이면, 컴파일 에러를 보고합니다(모호성).
    • 이 함수의 리턴 타입이 Unit 인지를 확인하고, 그렇지 않으면 컴파일 에러를 보고합니다.
    • 위의 모든 경우를 통과하면, 이 함수를 사용하여 연산하는 코드를 생성합니다.
  • 이런 함수가 없었다면, 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 이며, x == null 에 대해 null이 아닌 x 에 대해서는 항상 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 연산자들은 위임된 프로퍼티 문서에 기술되어있습니다.

이름이 있는 함수를 사용한 중위 호출

중위 함수 호출을 통해 커스텀 중위 연산을 추가할 수 있습니다.

이 페이지가 도움이 되셨다면, 원문 페이지에 방문해 엄지척을 해주세요!