Post

(Kotlin) 프로퍼티, 커스텀 접근자, 지연 초기화

프로퍼티 = 필드 + 접근자

  • 클래스 내부의 변수 선언은 자바에서는 필드 선언을 의미하지만 코틀린에서는 프로퍼티 선언을 의미한다.
  • 즉, 필드 뿐만 아니라 접근자 메서드도 알아서 생성해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person(
    val name: String,       // val은 읽기 전용 프로퍼티. private 필드와 getter.
    var isMarried: Boolean  // private 필드와 getter, setter.
)


/* 코틀린에서 호출할 때 (자동으로 getter, setter 호출로 변환해준다) */
person.name
person.isMarried = true


/* 자바에서 호출할 때 */
person.getName()
person.isMarried()    // boolean 프로퍼티 getter는 is prefix다
person.setMarried(true)

* 반대로 자바로 작성한 클래스도 프로퍼티 명명 형식을 맞춰 준다면 코틀린 클래스 프로퍼티처럼 사용할 수 있다.

커스텀 접근자

단순히 값을 반환하는 접근자가 아니라 특정 연산을 수행한 결과를 반환해야 하는 경우라면, 접근자를 직접 작성할 수 있다.

1
2
3
4
5
val isSquare: Boolean
    get() = height == width

var counter: Int = 0
    private set

isSquare 예제 처럼 호출될 때 마다 계산해서 수행 결과를 반환하는 방법이 있고,
초기화시, 그리고 종속 변수 값이 변경될 때 마다 변수 값을 다시 계산해두고 호출될 때는 단순히 계산 결과를 반환하기만 하도록 구성하는 방법이 있는데
전자는 호출 오버헤드가, 후자는 메모리 오버헤드가 발생한다. 특별히 많이 호출되지 않는다면 전자로 짜는게 더 보기 좋은 듯.

[!info] lateinit은 커스텀 접근자를 사용할 수 없으니 주의!

setter 내에서 변수 값 접근은 field 키워드로

1
2
3
4
5
6
7
class User (val name: String) {
    var address: String = "unspecified"
    set(value: String) {
        println("Address was changed for $name: $field -> $value")
        field = value
    }
}

* 커스텀 접근자를 지정할 때 내부에서 field 키워드를 사용하지 않는다면 뒷받침 필드가 필요 없다는 의미 이므로 알아서 필드가 생성되지 않는다 (매 호출 마다 eval 된다)

data class에서 custom getter 사용하고 싶을 때

backing property를 사용한다.

1
2
3
4
data class Test(private val _value: Int) {
    val value
        get() = value
}

지연 초기화

  • 안드로이드에서 액티비티 초기화에 사용하는 onCreate같이 생성자 안에서 초기화하지 않고 특별한 메소드에서 초기화해야 하는 경우 등, 지연 초기화 하고 싶은 경우 일단 변수에 null을 집어넣고 이후에 초기화해야 하는 방식이 떠오른다.
  • 그러나 변수에 일단 null을 집어넣으려면 타입을 Type?로 선언해주어야 하는데, 이러면 이 변수를 가져다 쓰는 모든 구간에서 널체크를 해줘야 하므로, 굉장히 비효율적이다.
  • 지연 초기화 하면서도, 타입을 non-nullable로 사용하는 방법은?

backing property를 사용한 lateinit

1
2
3
4
5
6
7
8
9
10
class Person {
    private var _emails: List<Email>? = null
    val emails: List<Email>
        get() {
            if (_emails == null) {
                _emails = loadEmails(this)
            }
            return _emails!!    // var 이라 스마트 캐스팅이 작동하지 않으므로 !!
        }
}
  • 가능한 방법이지만, 지저분하고, thread safe 하지도 않다.

val 지연 초기화 : by lazy

1
2
class Person {
    val emails by lazy { loadEmails(this) }
  • 부대 코드가 없어 깔끔하게 끝난다.
  • lazy 함수는 기본적으로 thread safe하다.
    • 하지만 필요에 따라 동기화에 사용할 락을 lazy에 전달 할 수도 있고, 반대로 lazy 함수가 동기화를 하지 못하게 막을 수도 있다.
  • by를 이용해서 프로퍼티 위임한 것인데, 상세 내용은[Kotlin] delegate 키워드 : by

var 지연 초기화 : lateinit

1
2
3
4
5
6
7
8
9
10
class LateInitTest {
    lateinit var myService: MyService

    @Before fun setUp() {
        myService = MyService()
    }
}

// 초기화 되었는지 체크는
::myService.isInitialized
  • 초기화하기 전에 프로퍼티에 접근하면 예외가 발생하며, 단순 NPE가 발생하는 것 보다 확인하기 용이하다.
  • Note ) lateinit modifier is not allowed on properties of primitive types

lateinit VS Delegates.notNull()

codechacha.com/ko/diff-between-deligate-and-lateinit-in-kotlin/

lateinit은 Primitive type에는 사용할 수 없다.

This post is licensed under CC BY 4.0 by the author.