Kotlin 공부(kotlin in action)/7장

2023 01 13 코틀린 공부 - 연산자 오버로딩과 기타 관계

posite 2023. 1. 13. 23:39

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 변경자가 붙는다.