코틀린에서 관례(convention)를 사용하는 하나의 예는 산술 연산자다. 자바에서는 원시 타입에 대해서만 산술 연산자를 사용할 수 있고, 추가로 String에 대해 + 연산자를 사용할 수 있다. 이러한 기술들을 convention
이라고 하는데, 이 장에서는 다양한 convention과 그들을 사용하는 예를 보려고 한다. convention을 사용함으로써 이미 존재하는 자바 클래스를 수정하지 않고도 확장하는 것이 가능하다.
data class Point(val x: Int, val y: Int) {
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
}
>>> val p1 = Point(10, 20)
>>> val p2 = Point(30, 40)
>>> println(p1 + p2)
Point(x=40, y=60)
operator 키워드를 plus 함수를 선언할 때 써 주면, + 기호로 두 Point 객체를 더할 수 있다. 이는 해당 함수를 해당 규칙의 구현으로 사용할 의도가 있으며 실수로 일치하는 이름을 가진 함수를 정의하지 않았음을 명시한다.
참고: 오버로딩 가능한 이항 산술 연산자
Expression | Function name |
---|---|
a * b | times |
a / b | div |
a % b | mod |
a + b | plus |
a - b | minus |
코틀린에서는 operator를 오버로딩하는 것이 비교적 간단한데, 그 이유는 자신만의 operator를 선언하는 것이 불가능하며 코틀린에서 제한하고 있는 set로만 가능하기 때문이다. 또한 각 Expression은 그와 짝이 맞는 Function name이 존재한다. 해당 짝 목록은 옆에 표와 같다. 이러한 연산자의 연산 순서는 산술 연산자와 같다. 예를 들어, a + b * c 은 항상 *먼저 계산되며, + 는 나중에 계산된다.
operator fun Char.times(count: Int): String {
return toString().repeat(count)
}
>>> println('a' * 3)
aaa
위의 식에서는 Char
와 Int
를 각각 연산항으로 가진다. 그 결과는 String
이다. 코트린에서는 이런 식의 조합 또한 완벽하게 수용 가능하다.
<aside> 💡 참고: 비트 연산자 코틀린에서는 비트 연산자에 대해 자신만의 타입을 설정할 수 있도록 지원하지 않는다. 이러한 연산에 대해서는 중위 표기법(infix call syntax)으로 표현하는 일반 함수만 지원된다.
>>> println(0x0F and 0xF0)
0
>>> println(0x0F or 0xF0)
255
>>> println(0x1 shl 4)
16
</aside>
plus라고 operator를 정의내릴 때, 코틀린은 +
뿐만 아니라 **+=
**까지 지원한다. 코틀린 표준 라이브러리는 변경 가능한 컬렉션에 대해plusAssign
을 정의할 수 있다. 다른 산술 연산자도 minusAssign
, timesAssign
, …등으로 연산 가능하다.
>>> var point = Point(1, 2)
>>> point += Point(3, 4)
>>> println(point)
Point(x=4, y=6)
이론적으로, 코드에서 +=를 사용할 때, plus와 plusAssign 함수가 모두 호출될 수 있다. 이런 경우에 두 함수가 모두 정의되어 있고 실행 가능한 상황이라면, 컴파일러는 오류를 호출할 것이다. 이럴 경우 해결할 수 있는 방법은 operator를 regular function call로 바꾸는 것이다. 또 다른 방법은 var을 val로 바꾸어 plusAssign을 실행 불가능하게 만드는 것이다.
하지만 일반적으로는 새로운 클래스를 만드는 것이 좋다. 즉, 오직 plusAssign와 비슷한 다른 연산을 지원하는 변경 가능한 클래스를 만들면 된다.
여태까지는 이항 연산자 오버로딩에 대해 이야기했다. 이제 단항 연산자 오버로딩을 이야기해보자.
operator fun Point.unaryMinus(): Point {
return Point(-x, -y)
}
>>> val p = Point(10, 20)
>>> println(-p)
Point(x=-10, y=-20)
단항 연산자의 경우 함수는 그 어떠한 인수도 받지 않는다. 단항 연산자는 이항 연산자와 같은 방식으로 작동한다. 사용할 수 있는 연산자 목록은 아래 표와 같다.