chapter 7-2. Operator overloading and other conventions

Destructuring declarations

Destructuring declaration(구조 분해 선언)을 사용하는 방법을 보자.

val p = Point(10, 20)
val (x, y) = p

이렇게 여러 변수의 초기화가 가능한 방식이다.

  • python도 여러 변수의 초기화를 하지만 kotlin과는 다르다.

destructuring은 다음과 같은 convention을 사용한다.
destructuring

  • 초기화 할 좌변의 변수들을 괄호로 묶어야 한다.
  • destructuring declaration은 초기화를 위해 componentN 함수를 호출한다. 여기서 N은 destructuring declaration의 변수 위치에 따라 붙는 번호다.
  • data class는 생성자에 있는 property에 대해 자동으로 componentN 함수를 만들어준다.
  • kotlin standard libary에서는 맨 앞의 다섯 property에 대해서만 componentN을 제공한다.
  • collection에 대해서도 destructuring이 가능하다.

data class가 아닌 경우의 구현

class Point(val x: Int, val y: Int) {
    operator fun component1() = x
    operator fun component2() = y
}

Destructuring in loop

변수 선언이 들어갈 수 이쓴ㄴ 장소라면 어디든 구조 분해 선언을 사용할 수 있다.

  • for 안에서도 사용할 수 있다.

특히 map에서 유용하게 사용할 수 있다.

fun printEntries(map: Map<String, String>) {
    for ((key, value) in map) {
        println("$key -> $value")
    }
}

delegated property

field에 단순 저장하는 것보다 더 복잡하게 작동하는 property를 쉽게 구현할 수 있다.

  • delegate를 활용해서 값을 field가 아니라 db table이나 browers session, map 등에도 저장할 수 있다.
// delegate의 일반적인 문법
class Foo {
    var p: Type by Delegate()
}

// compiler에서 해석해서 생기는 코드
class Foo {
    private val delegate = Delegate()
    var p: Type
    set(value: Type) = delegate.setValue(..., value)
    get() = delegate.getValue(...)
}

위에서 예시로 사용한 Delegate class 처럼 사용하려면 getValuesetValue 함수를 가지고 있어야 된다.

lazy initialization

lazy initialization는 객체의 일부를 초기화하지 않고 남겨뒀다가 필요할 경우 초기화할 때 쓰는 패턴이다.
아래는 email을 실제 사용할 때 한 번만 초기화하도록 구현한 class 이다.

class Person(val name: String) {
    private var _emails: List<Email>? = null // 이런걸 backing property라고 함.
    val emails: List<Email>
    get() {
        if (_emails == null) {
            _emails = loadEmails(this)
        }
        return _emails!!
    }
}

email은 not nullable하기 때문에 _email을 사용해서 활용해야 한다.

class Person(val name: String) {
    val emails by lazy { loadEmails(this) }
}

lazygetValue 함수가 있는 객체를 반환한다.
lazyby와 함께 사용되면 delegate property를 만들 수 있다.
lazy 함수는 기본적으로 thread safe 하다.

  • 필요에 따라 동기화에 사용할 락을 lazy 함수에 전달할 수 있다.
  • multi thread에서 사용하지 않을 property를 위해 lazy 함수가 동기화하지 못하게 막을 수도 있다.

implement delegate property

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) { // KProp은 나중에 다룸. name을 가져올 수 있다는 것만 알자.
        val oldValue = propValue
        propValue = newValue
        changeSupport.firePropertyChange(prop.name, oldValue, newValue) // noti하기 위한 따로 구현된 함수라고만 생각하자
    }
}

class Person(val name: String, age: Int, salary: Int): PropertyChangeAware() {
    var age: Int by ObservableProperty(age, changeSupport)
    var salary: Int by ObservableProperty(salary, changeSupport)
}

by 오른쪽에 오는 객체를 delegate 객체라고 한다.
진짜 객체의 property를 읽거나 쓸 때마다 delegate 객체의 getValue와 setValue를 호출한다.

getValuesetValue에도 operator가 붙는다.

위 코드를 kotlin standard에 있는 Delegates를 이용해서 아래와 같이 바꿀 수 있다.

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)
}

by의 우항에는 꼭 새로운 instance가 생성되어야 하는 것은 아니다.
getValuesetValue를 포함하는 객체를 반환하는 함수 호출이나 다른 property, 다른 expression이 올 수 있다.

delegate property rule

delegate가 어떻게 동작하는지 정리해본다.

class C {
    var prop: Type By MyDelegate()
}

compiler는 MyDelegate class의 instance를 hidden property에 저장한다.

  • 이걸 <delegate>라고 부른다.
    compiler는 KProperty type의 object를 property를 표현하기 위해 사용한다.
  • 이걸 <property>라고 부른다.
class C {
    private val <delegate> = MyDelegate()
    var prop: Type
    get() = <delegate>.getValue(this, <property>)
    set(value: Type) = <delegate>.setValue(this, <property>, value)
}

delegate

이렇게 compiler가 property의 접근자에 대해 get/setValue 호출 코드를 생성해준다.

framework에서 delegate 활용

object Users : IdTable() { // db table
    val name = varchar("name", length = 50).index() // property = column
    val age = integer("age")
}

class User(id: EntityID) : Entity(id) { // 각 User instance는 table에 들어있는 구체적인 entity에 해당
    var name: String by Users.name
    var age: Int by Users.age
}

Users는 db 전체에 단 하나만 있는 table을 표현하므로 singleton으로 선언됨.
위 같은 코드에서는 User에 접근할 때 entity에 정의된 db에서 값을 가져오므로 편리하다.

F/W는 Column class 안에 get/setValue를 정의한다.
get/setValue는 kotlin delegate conventions의 요구사항을 만족한다.

operator fun <T> Column<T>.getValue(o: Entity, desc: KProperty<*>): T {
    // retrieve the value from the database
}
operator fun <T> Column<T>.setValue(o: Entity, desc: KProperty<*>, value: T) {
   // update the value in the database
}