코틀린에서는 특정 연산자의 역할을 함수로 정의할 수 있습니다. 이를 Convention(관례)이라고 합니다.
가장 기본적인 예로는 산술 연산자가 있습니다.
자바에서는 원시 타입(primitive)에 대해서만 산술 연산자를 사용할 수 있고, 추가로 String에 대해 "+" 연산자를 사용할 수 있습니다.
그러나 다른 클래스에서도 이러한 산술 연산자가 유용한 경우가 있기 마련입니다.
지금부터, 어떻게 클래스에 대한 일반 산술 연산자를 정의할 수 있는지에 대해 살펴보도록 하겠습니다.
이항 산술 연산 오버로딩
코틀린에서는 +, - 같은 산술 연산자를 오버로딩해서 사용할 수 있습니다.
객체끼리 더하거나 뺄 때, 원하는 동작을 함수 안에 구현하면 연산자를 통해 이를 표현할 수 있습니다.
/* plus 연산자 구현하기 예제 */
data class Point(val x: Int, val y: Int) {
operator fun plus(other: Point): Point { //"plus" 라는 연산자 함수를 정의합니다.
return Point(x + other.x, y + other.y)
}
}
>>> val point1 = Point(10, 20)
>>> val point2 = Point(30, 40)
>>> println(point1 + point2) //"+"로 계산하면 "plus"함수가 호출됩니다.
Point(x=40, y=60)
연산자를 오버로딩하는 함수 앞에는 plus 함수와 같이 함수명 앞에 operator 키워드를 붙여야 하며,
operator 키워드를 붙임으로써 이 함수가 Convention(관례)을 따르는 함수임을 명확히 할 수 있습니다.
만약, operator 없이 함수명을 우연히 plus로 했다면 "operator modifier is required...(operator 키워드를 추가해야함)" 이라는 오류가 뜨기 때문에 개발자가 문제를 해결할 수 있습니다.
위의 예제에서 operator plus 함수를 선언하고 나면 이제 "+" 기호로 두 Point 객체를 더할 수 있게 됩니다.
이러한 연산자를 멤버 함수로 만드는 대신 확장 함수로도 정의할 수 있고, 보통 operator 선언은 확장 함수로 만들어서 주로 사용합니다.
/* 연산자를 확장 함수로 구현하기 */
operator fun Point.plus(other: Point) : Point {
return Point(x + other.x, y + other.y)
}
이 구현도 앞에 구현과 동일하게 "+" 기호로 사용할 수 있습니다.
코틀린은 언어에서 미리 정해둔 연산자만! 오버로딩할 수 있기 때문에, 다른 언어와 비교해서 오버로딩 연산자를 정의하고 사용하는 것이 더 쉽고 편리합니다.
식 | 함수 이름 |
a * b | times |
a / b | div |
a % b | mod (version 1.1 이상부터는 rem) |
a + b | plus |
a - b | minus |
연산자를 정의할 때 두 피연산자(연산자 함수의 두 파라미터)가 같은 타입일 필요는 없습니다.
예를 들어 Point 객체를 어떤 비율에 따라 확대/축소하는 연산자를 정의하는 예제로 알아보도록 하겠습니다.
/* 두 피연산자의 타입이 서로 다른 연산자 정의하기 */
operator fun Point.times(scale: Double) : Point {
return Point( (x * scale).toInt(), (y * scale).toInt() )
}
>>> val point = Point(10, 20)
>>> println(point * 1.5)
Point(x=15, y=30)
또한, 연산자 함수의 반환 타입이 꼭 두 피연산자와 일치하지 않아도 됩니다.
예를 들면 Char 타입과 Int 타입으로 연산을 하고 결과는 String으로 반환하는 연산자 조합도 완전히 합법적이며 유용하게 사용되는 오버로딩 케이스입니다!
/* 결과 타입이 피연산자 타입과 전혀 다른 연산자 정의하기 */
operator fun Char.times(count: Int) : String {
return this.toString().repeat(count)
}
>>> println('A' * 3)
AAA
복합 대입 연산자 오버로딩
"+=", "-=" 등의 연산자를 복합 대입 연산자라고 하며, "+" 대응 함수인 plus와 같은 연산자를 오버로딩하면 코틀린은 그와 관련된 연산자인 "+=" 도 자동으로 구현해줍니다. 👍
>>> var point = Point(1, 2)
>>> point += Point(3, 4) // point = point + Point(3, 4)와 동일
>>> println(point)
Point(x=4, y=6)
단항 연산자 오버로딩
위에 내용까지는 두 피연산자 값에 작용하는 이항 연산자에 대해서 살펴보았고, 이번에는 하나의 값에만 작용하는 단항 연산자에 살펴보겠습니다.
단항 연산자를 오버로딩하는 방법도 이항 연산자와 마찬가지로 미리 정해진 함수를 선언하면서 operator로 표시하면 됩니다.
/* 단항 연산자 정의하기 */
operator fun Point.unaryMinus() : Point { //단항연산자는 파라미터가 없습니다.
return Point(-x, -y) //각 좌표에 -(음수)를 취한 좌표를 반환
}
>>> val point = Point(10, 20)
>>> println(-point)
Point(x = -10, y = -20)
식 | 함수 이름 |
+a | unaryPlus |
-a | unaryMinus |
!a | not |
++a, a++ | inc |
--a, a-- | dec |
"++" 와 "--" 의 경우 inc(), dec()만 구현해두면 알아서 전위와 후위 증가/감소 연산을 해줍니다.
/* 장그 연산자 정의하기 */
operator fun Int.inc() = this + 2
>>> var number = 0
>>> println(number++)
0
>>> println(number)
2
>>> printlin(++number)
4
비교 연산자 오버로딩 - 동등성 연산자 : equals
코틀린에서는 모든 객체에 대해 비교 연산을 수행하는 경우, equals나 compareTo를 호출해야 하는 자바와는 달리 "==" 비교 연산자를 직접 사용할 수 있어서 비교하는 코드가 더 간결하며 이해하기 쉽습니다. 👍
코틀린은 "==" 연산자 호출을 equals()로 컴파일하며, "!=" 역시 equals() 를 사용하여 결과 값을 not 처리하는 식으로 동작합니다.
a == b 라는 코드는 실제로 내부에서 인자의 null 체크를 하므로 다른 연산과 달리 null이 될 수 있는 값에도 적용할 수 있는 이유입니다.
실제 내부 코드는 아래와 같이 구현되어 있습니다.
이 경우 a가 null인지 판단해서 null이 아닌 경우에만 a.equals(b)가 호출되고, 만약 a가 null이라면 b도 null인 경우에만 true가 반환됩니다.
class Point(val x: Int, val y: Int) {
...
override fun equals(other: Any?): Boolean {
return super.equals(other)
}
...
}
equals() 함수 앞에는 override가 붙어있는 것을 볼 수가 있는데, equals()는 Any에 정의된 함수이므로 override가 필요합니다.
아래는 Any 클래스의 구현이며, equals() 함수 앞에 operator 가 명시되어 있는 것을 확인할 수 있습니다.
package kotlin
/**
* The root of the Kotlin class hierarchy. Every Kotlin class has [Any] as a superclass.
*/
public open class Any {
...
public open operator fun equals(other: Any?): Boolean
...
public open fun hashCode(): Int
...
public open fun toString(): String
}
그렇기 때문에, 하위 클래스에서는 override를 해서 사용할 수 있지만, Any가 최상위 객체이며 Any를 상속받는 equals()가 확장 함수보다 우선순위가 높기 때문에 equals()는 사실상 확장 함수로 재정의해서 사용할 수가 없다는 사실에 유의해야 합니다!
비교 연산자 오버로딩 - 순서 연산자 : compareTo
자바에서 정렬이나 최댓값, 최솟값 등 값을 비교해야 하는 알고리즘에는 Comparable 인터페이스를 사용합니다.
Comparable의 compareTo 메소드를 이용해서 한 객체와 다른 객체의 크기를 비교해 정수로 나타내 줍니다.
하지만 자바에서는 이 메소드를 짧게 호출할 수 있는 방법이 없어서 항상 object1.compareTo(object2) 의 형태로 명시적으로 사용해야 합니다.
코틀린도 똑같은 Comparable 인터페이스를 지원하며, 게다가 코틀린은 Comparable 인터페이스 안에 있는 compareTo 메소드 호출의 convention을 제공하여 비교 연산자 ( < , > , <= , >= ) 는 compareTo 호출로 컴파일이 됩니다.
이와 같이 Comparable 인터페이스를 구현하는 모든 자바 클래스를 코틀린에서는 간결한 연산자 구문으로 비교할 수 있습니다.
'코틀린' 카테고리의 다른 글
코틀린(Kotlin) - 프로퍼티 접근자 로직 재사용 : 위임 프로퍼티 (0) | 2021.04.27 |
---|---|
코틀린(Kotlin) - 구조 분해 선언과 component 함수 (0) | 2021.04.25 |
코틀린(Kotlin) - 컬렉션과 배열 (0) | 2021.04.17 |
코틀린(Kotlin) - 원시 타입(primitive type) (0) | 2021.04.14 |
코틀린(Kotlin) - 타입 파라미터의 null 가능성 (0) | 2021.03.30 |