어떤 언어 기능과 미리 정해진 이름의 함수를 연결해주는 기법을 코틀린에서는 관례라고 부른다. 언어 기능을 타입에 의존하는 자바와 달리 코틀린은 관례에 의존한다.
1. 이항 산술 연산 오버로딩
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가 있어야 한다. operator 키워드를 붙임으로써 어떤 함수가 관례를 따르는 함수임을 명확히 할 수 있다. operator 변경자를 추가해 plus 함수를 선언하고 나면 + 기호로 두 Point 객체를 더할 수 있다.
operator fun Point.minus(other:Point):Point{
return Point(x-other.x, y-other.y)
}
연산자를 멤버 함수 대신 확장 함수로 정의할 수도 있다.
- 코틀린에서는 프로그래머가 직접 연산자를 만들어 사용할 수 없고 언어에서 미리 정해둔 연산자만 오버로딩할 수 있으며 관례에 따르기 위해 클래스에서 정의해야 하는 이름이 연산자별로 정해져 있다. (연산자 우선순위는항상 표준 숫자 타입에 대한 연산자 우선순위와 같다.)
식 | 함수 이름 |
a * b | times |
a / b | div |
a % b | mod |
a + b | plus |
a - b | minus |
operator fun Point.times(scale:Double):Point{
return Point((x*scale).toInt(), (y*scale).toInt())
}
>>>val p = Point(10, 20)
>>>println(p*1.5) //p.times(1.5)로 컴파일
Point(x=15, y=30)
연산자를 정의할 때 두 피연산자가 같은 타입일 필요는 없다. 다만, 코틀린 연산자가 자동으로 교환 법칙을 지원하지는 않기 때문에 p * 1.5가 아닌 1.5 * p를 쓰고 싶다면 operator fun Double.times(p:Point):Point를 따로 정의해야 한다.
operator fun Char.times(count:Int):String{
return toString().repeat(count)
}
>>>println('a'*3)
aaa
연산자 함수의 반환 타입이 꼭 두 피연산자 중 하나와 일치해야만 하는 것도 아니다. 이 연산자는 Char을 좌항, Int를 우항으로 받아서 String을 반환한다.
2. 복합 대입 연산자 오버로딩
- plus와 같은 연산자를 오버로딩하면 코틀린은 + 연산자뿐 아니라 그와 관련 있는 연산자인 +=도 자동으로 함께 지원한다. +=, -= 등의 연산자는 복합 대입 연산자라 불린다.
>>>var point = Point(1, 2)
>>>point += Point(3,4)
>>>println(point)
Point(x=4, y=6)
operator fun <T> MutableCollection<T>.plusAssign(element:T){
this.add(element)
}
반환 타입이 Unit인 plusAssign 함수를 정의하면 코틀린은 += 연산자에 그 함수를 사용한다. 다른 복합 대입 연산자 함수도 비슷하게 minusAssign, timesAssign 등의 이름을 사용한다. 코틀린 표준 라이브러리는 변경 가능한 컬렉션에 대해 위와 같이 plusAssign을 정의한다.
>>>val numbers = ArrayList<Int>()
>>>numbers += 42
>>>println(numbers[0])
42
다음의 예제에 +=는 위의 정의된 plusAssign을 사용한다.
- 어떤 클래스가 plus와 plusAssign을 모두 정의하고 둘 다 +=에 사용 가능한 경우 컴파일러는 오류를 보고한다. 일반 연산자를 사용하거나, var을 val로 바꿔서 plusAssign 적용을 불가능하게 해 해결할 수 있다. 하지만 두 연산을 동시에 정의하지 않는 것이 가장 좋다. 클래스가 변경 불가능하다면 plus와 같이 새로운 값을 반환하는 연산만을 추가하고, 빌더와 같이 변경 가능한 클래스라면 plusAssign과 같은 연산만을 제공하여 일관성 있게 설계하도록 한다.
- 코틀린 표준 라이브러리는 컬렉션에 대해 두가지 접근 방법을 함께 제공한다. +와 -는 항상 새로운 컬렉션을 반환하고, +=, -= 연산자는 항상 변경 가능한 컬렉션에 작용해 메모리에 있는 객체 상태를 변화시킨다. -=, +=가 읽기 전용 컬렉션에 적용될 경우 변경을 적용한 복사본을 반환한다.
val list = arrayListOf(1,2)
list += 3 //+=는 list를 변경한다.
val newList = list + listOf(4,5) //+는 두 리스트의 모든 원소를 포함하는 새로운 리스트를 반환한다.
>>>println(list)
[1,2,3]
>>>println(newList)
[1,2,3,4,5]
3. 단항 연산자 오버로딩
- 단항 연산자를 오버로딩하기 위해 사용하는 함수는 인자를 취하지 않는다.
operator fun Point.unaryMinus():Point{
return Point(-x, -y)
}
식 | 함수 이름 |
+a | unaryPlus |
-a | unaryMinus |
!a | not |
++a, a++ | inc |
--a, a-- | dec |
operator fun BigDecimal.inc() = this + BigDecimal.ONE
>>>var bd = BigDecimal.ZERO
>>>println(bd++) //println이 실행된 다음 값을 증가시킴
>>>println(++bd) //값을 증가시키고 println 실행
'Dev Book > Kotlin IN ACTION' 카테고리의 다른 글
CH7(7.3). 컬렉션과 범위에 대한 관례 (0) | 2022.02.18 |
---|---|
CH7(7.2). 비교 연산자 오버로딩 (0) | 2022.02.15 |
CH6(6.3). 컬렉션과 배열 (0) | 2022.02.06 |
CH6(6.2). 코틀린의 원시 타입 (0) | 2022.01.21 |
CH6(6.1-2). 널 가능성 (0) | 2022.01.20 |