본문 바로가기

코틀린

코틀린: 클래스 속성 - getter와 setter

클래스 속성(property)에 관한 보충 설명입니다. 자세한 내용은 쉽게 다가가는 최신 프로그래밍: 코틀린 - 4.2 클래스 속성을 참고하기 바랍니다.

클래스 속성, 그냥 변수아냐? 아닙니다. 클래스 속성은 기본적으로 변수이지만 연관된 함수(getter 또는 setter)도 정의할 수 있습니다. 코틀린에서는 속성에 대한 getter, setter를 자동 생성한다던데... 반은 맞고 반은 틀립니다. 속성에 대한 getter와 setter도 목적에 맞게 정의할 수 있습니다. 여러분이 혹시 놓치고 있을지도 모를 클래스 속성에 대한 모든 것을 알아보겠습니다.

수식어 val을 사용한 속성 : 사용자 정의 getter 
주 생성자에서 정의한 속성(name, age)에 대해서는 사용자 정의 getter (또는 setter)를 정의할 수 없습니다. 클래스 Person 블록 안에서 선언한 속성에 대해서만 getter(또는 setter)를 정의할 수 있습니다. 여기서는 속성 isAdult에 대해 사용자 정의 getter를 정의합니다. kim.isAdult처럼 속성 isAdult 참조할 때 getter가 호출되면서 속성을 초기화합니다.

class Person (val name: String, var age: Int) {
    val isAdult: Boolean
        get() = age >= 18
}

fun main() {
    val kim = Person("Kim", 19)
    println(kim.isAdult) // true
}

 

위 코드와 아래 코드를 비교해 볼까요. 뭐가 달라졌을까요? 

class Person {
    val name: String = "Kim"
        get() = field
    val age: Int = 17
        get() = field
    val isAdult: Boolean
        get() = this.age >= 18
}

fun main() {
    val kim = Person()
    println(kim.name)
    println(kim.isAdult)
}

주 생성자에서 선언했던 속성(name, age)을 클래스 블록으로 옮겨와 getter()를 정의했습니다. get()은 field를 반환합니다.

val name: String = "Kim"
    get(): String {
        return field
}

 

■ field(백업 필드) - 속성을 초기화해야 getter/setter에서 field를 사용할 수 있습니다.
field는 백업 필드(backing field)로써, 속성의 초깃값을 저장하고 있습니다. 코드를 보면 val name: String = "Kim"과 같이 속성을 초기화했습니다(속성 초기화 과정에서는 setter를 호출하는 것이 아니라 초깃값을 field에 저장합니다). 이렇게 속성을 초기화해야만 field를 사용할 수 있기 때문입니다. kim.name으로 속성 name을 참조하면, getter는 field에 저장된 값을 반환합니다.

그럼 속성 isAdult는 어떨까요? 속성 isAdult의 get()에서는 field를 사용할 수 없습니다. 속성 isAdult를 초기화하지 않았기 때문이죠. isAdult에 대한 백업 필드가 없기 때문에, 속성 isAdult를 참조할 때마다 get()을 다시 실행해 값을 구합니다.

■ 수식어 var를 사용한 속성 : 사용자 정의 getter와 setter
수식어 var를 사용해 선언한 속성은 사용자 정의 getter와 setter를 정의할 수 있습니다. 아래 코드에서 속성 age는 자동으로 생성된 getter(get() = field)를 사용하며, 사용자 정의 setter만 직접 구현했습니다. kim.age = -1 과 같이 속성 age 값을 변경할 때 setter가 호출됩니다.

class Person (val name: String) {
    var age: Int = 0
        get() = field // default getter. 생략할 수 있음.
        set(value) {
            if (value >= 0) {
                field = value
            } else {
                println("Age cannot be negative")
            }
        }
}

fun main() {
    val kim = Person("Kim")
    kim.age = -1
    println(kim.age)  // 0
}

 

■ 가시성 수식어(visibility modifier)를 붙인 속성 
속성에 대한 사용자 정의 getter와 setter에도 가시성 수식어를 붙일 수 있습니다. 가시성 수식어 중 private을 주로 사용하며, 가변 속성의 값을 클래스 밖에서 변경하지 못하도록 일반적으로 setter에 수식어 private 을 붙입니다. 자동으로 생성되는 getter나 setter에만 적용할 경우 get 또는 set 앞에 키워드 private를 붙이면 됩니다.

아래 예는 속성 age를 클래스 밖에서 바꾸지 못하도록 기본 setter(private set)를 정의했습니다. 올해 년도를 입력하면 속성 currentYear가 바뀌면서 속성 age도 함께 바뀝니다. 속성 age에 대한 getter는 따로 정의하지 않았지만, 기본 가시성은 public이며 외부에서 속성 age를 참조할 수 있습니다. println(kim.age)를 실행하면 현재 나이를 정확히(?) 알 수 있습니다.

import java.time.LocalDate

class Person (val name: String, val birthYear: Int) {
    var age: Int = 0
        private set
    var currentYear: Int = 2000
        set(value) {
            field = value
            age = currentYear - birthYear
        }
}

fun main() {
    val kim = Person("Kim", 2000)
    kim.currentYear = LocalDate.now().year
    println(kim.age)  
}