2023 01 13 코틀린 공부 - 연산자 오버로딩과 기타 관계
7장 연산자 오버로딩과 기타 관례
관례의 가장 단순한 예는 산술연산자다. +,-,/,*,%등의 기본전인 연산자들이 있다. 어떤 함수가 관례를 따름을 나타내는 키워드로 함수 앞에 operator 키워드를 붙여야한다. 아래의 코드는 Point의 +연산자를 x, y좌표를 확장함수로 정의한 것이다.
data class Point(var x: Int, var y: Int)
operator fun Point.plus(other: Point): Point{
return Point(x+other.x,y+other.y)
}
또한 +=, -=과 같은 복합대입연산자도 있다. 이들은 변경 가능한 컬렉션에 작용해 메모리에 있는 객체의 상태를 변화시킨다. 위의 연산들과 달리 한 값에만 적용하는 단한 연산자도 있다. ++, --가 그 예이다.
equals, ==, !=, ===등의 비교연산자도 있다. a==b는 a가 null이 아닐 때 a.equals(b)를 수행하고 a가 null이면 b도 null일때 참이 된다. ===는 같은 객체를 가리키는지 비교한다.
순서연산자 <,>,<=,>=는 compareTo도 있어서 정렬, 최댓값, 최솟값등을 구할 수 있다.
컬렉션의 원소 접근 get, set도 오버로드 할 수 있다.
operator fun Point.get(index: Int): Int {
return when(index){
0 -> x
1 -> y*2
else ->
throw IndexOutOfBoundsException("Invalid coordinate $index choose 0 or 1")
}
}
val p = Point(3,4)
println(p[1])
operator fun Point.set(index: Int, value: Int){
when(index){
0 -> x = value
1 -> y = value
else ->
throw IndexOutOfBoundsException("Invalid coordinate $index choose 0 or 1")
}
}
val p = Point(3,4)
println(p[1])
p[0]= 100
println(p[0])
컬렉션의 다른 연산자로는 in이 있다. 이 또한 관례로 구현할 수 있다.
operator fun Rectangle.contains(p:Point):Boolean{
return p.x in upperLeft.x until lowerRight.x && p.y in upperLeft.y until lowerRight.y
}
val rect = Rectangle(Point(100,4),Point(200,8))
println(Point(150,6) in rect)
범위를 만드는 rangeTo도 관례로 만들 수 있다. 이 함수는 범위를 반환한다. 아래의 코드는 결국 1..3을 의미한다.
for x in list처럼 컬렉션의 iterator도 구현할 수 있다.
println(1.rangeTo(3))
구조분해를 통해서 여러 다른 변수를 한번에 초기화할 수 있다.
val address = "시흥시 정왕동"
val (city, dong) = address.split(" ")
println("시 : $city 동 : $dong")
구조분해를 이용해 맵을 이터레이션을 할 수 있다.
fun printEntries(map: Map<String, String>){
for((key,value) in map){
println("$key -> $value")
}
}
val map = mapOf("Oracle" to "Java", "Jetbrains" to "Kotlin")
printEntries(map)
위임 프로퍼티는 값을 뒷받침하는 필드에 단순히 저장하는 것보다 더 복잡한 방식으로 작동하는 프로퍼티를 쉽게 구현 할 수 있다. 객체가 직접 작업을 수행하지 않고 다른 도우미 객체가 작업을 처리하게 맡기는 디자인 패턴이 위임이다. 이때 도우미 객체를 위임 객체라 한다. 이 위임 객체가 getValue, setValue등을 수행하게 된다.
class Delegate{
operator fun getValue(...){...}
operator fun setValue(..., value: Type){...}
}
class Foo{
var p: Type by Delegate()
}
또한 값이 필요할 때 초기화하는 지연 초기화를 위임 프로퍼티를 통해 구현하면 간단하게 구현 할 수 있다. 위임 프로퍼티에 by lazy{}를 주면 된다.
class Person (val name: String){
val emails by lazy { loadEmails(this) }
}
위임 프로퍼티 구현은 책에 있는 객체의 프로퍼티가 변경될 때 마다 변경 통지를 보낼 때 쓰는 PropertyChangeSupport와 PropertyChangeEvent 클래스를 이용한 예제로 해보았다.
open class PropertyChangeAware{
protected val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangeListener(listener: PropertyChangeListener){
changeSupport.addPropertyChangeListener(listener)
}
fun deletePropertyChangeListener(listener: PropertyChangeListener){
changeSupport.removePropertyChangeListener(listener)
}
}
class ObservableProperty(
var propValue: Int,
val changeSupport: PropertyChangeSupport
){
operator fun getValue(p: Person, prop: KProperty<*>): Int = propValue
operator fun setValue(p: Person, prop: KProperty<*>, newValue: Int) {
val oldValue = propValue
propValue = newValue
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
}
class Person(
val name: String, age: Int, salary: Int
): PropertyChangeAware(){
private val observer = {
prop: KProperty<*>, oldValue: Int, newValue: Int ->
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
var age : Int by Delegates.observable(age, observer)
var salary : Int by Delegates.observable(salary, observer)
}
프로퍼티 값을 저장하고 변경되면 자동으로 통지하는 클래스 ObservableProperty클래스를 만들었고 Person클래스에서 by를 이용해서 age, salary를 위임했다. 이때 도우미 클래스인 ObservableProperty클래스의 getValue, setValue에 operator 변경자가 붙는다.