연산자 오버로딩
Kotlin 은 타입들에 미리 정의된 연산자들의 동작을 수정 구현할 수 있도록 허용합니다. 이러한 연산자들은 미리 정의된 기호 표현이나 우선순위를 가지고 있습니다. 연산자를 구현하려면, 연산하려는 타입에 특정한 이름을 가지는 멤버 함수나 확장 함수를 구현하면 됩니다. 그 타입은 이항 연산들에 대해서는 연산자 좌측의 값이 되며, 단항 연산자에 대해서는 연산하려는 주체가 됩니다.
연산자를 오버로드하려면, 해당하는 함수를 operator
수정자로 표기하세요:
1interface IndexedContainer {
2 operator fun get(index: Int)
3}
4
오퍼레이터 오버로드를 재정의할 때는, operator
수정자를 생략할 수 있습니다:
1class OrdersList: IndexedContainer {
2 override fun get(index: Int) { /*...*/ }
3}
4
단항 연산자
단항 전위 연산자
표현 | 사용되는 함수 |
---|---|
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.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 + 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
라는 클래스에 대해 주어진 값으로부터 시작하여
오버로드된 +
연산자를 통해 그 값을 증가시킬 수 있는 예제입니다:
1data class Counter(val dayIndex: Int) {
2 operator fun plus(increment: Int): Counter {
3 return Counter(dayIndex + increment)
4 }
5}
6
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
함수로 변환됩니다.
호출 연산자
표현 | 사용되는 함수 |
---|---|
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
가 변경 가능한 변수이면서, 그 수학적 연산 함수의 결과가a
의 서브타입이면, 컴파일 에러를 보고합니다(모호성). - 이 함수의 리턴 타입이 Unit 인지를 확인하고, 그렇지 않으면 컴파일 에러를 보고합니다.
- 위의 모든 경우를 통과하면, 이 함수를 사용하여 연산하는 코드를 생성합니다.
- 해당 함수에 대한 수학적 이항 연산 함수(
- 이런 함수가 없었다면,
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
이며,
x == null
에 대해 null
이 아닌 x
에 대해서는 항상 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 |
모든 비교 연산은 Int
를 리턴하도록 강제되는 compareTo
로 변환됩니다.
프로퍼티 위임 연산자
provideDelegate
, getValue
, setValue
연산자들은 위임된 프로퍼티 문서에 기술되어있습니다.
이름이 있는 함수를 사용한 중위 호출
중위 함수 호출을 통해 커스텀 중위 연산을 추가할 수 있습니다.
이 페이지가 도움이 되셨다면, 원문 페이지에 방문해 엄지척을 해주세요!