실무 프로젝트로 배우는 Kotlin & Spring/코틀린 고급

지연 초기화

webmaster 2022. 10. 16. 23:07
728x90

지연 초기화는 대상에 대한 초기화를 미뤘다가 실제 사용 시점에 초기화하는 기법을 말한다.

초기화 과정에서 자원을 많이 쓰거나 오버헤드가 발생할 경우 지연 초기화를 사용하는 게 유리할 수 있으며, 싱글톤 패턴에서 지연 초기화나 JPA의 엔티티 LazyLoading과 같은 곳에서 자주 사용된다.

by lazy

class HelloBot {
    //var greeting: String? = null
    val greeting: String by lazy(/*LazyThreadSafetyMode.NONE*/) {
        //LazyThreadSafetyMode.NONE 가 설정되어 있다면, 멀티스레드에 안전하지 않다
        //LazyThreadSafetyMode.PUBLICATION 으로 설정되어 있다면, 동기화가 되지 않고 동작한다.
        println("초기화 로직 수행")
        getHello()
    } //불변을 유지하면서 초기화를 유지 할 수 있다.

    //by lazy를 사용하면, var를 사용할 수 없다
    fun sayHello() = println(greeting)
}

fun getHello() = "안녕하세요"

fun main() {
    /*
    //코드의 문제점 : HelloBot에 있는 변수가 가변변수(var)로 선언되어 있다.
    //지연 할당의 쓰려면 가변변수 밖에 쓸 수 없다 -> 코틀린은 by lazy를 사용한다.
    val helloBot = HelloBot()

    //... 특정 로직이 있다고 가정

    //helloBot.greeting = getHello()
    helloBot.sayHello()
    helloBot.sayHello()
    helloBot.sayHello()
     */
    val helloBot = HelloBot()
    //... 특정 로직이 있다고 가정
    for (i in 1..5) {
        Thread {
            helloBot.sayHello()//여러번 실행해도 초기화 로직은 한번만 실행된다(멀티 스레드에 안전하다)
        }.start()
    }
}
  • 기본적으로 변수를 선언할 때, 초기화 하지 않다가, 특정 시점 이후 초기화가 필요할 때는 나중에 값을 수정할 수 있게 var로 선언하여 가변으로 만들어야 한다.
    • var 키워드로 선언하는 것은 여러 가지 위험을 가지고 있으므로 될 수 있으면, 불변으로 유지하는 것이 좋다.
    • 코틀린에서 제공하는 by lazy를 사용하면 불변성을 유지하면서 지연 초기화가 가능하다
  • by lazy를 사용하면 사용 시점 1회만 초기화 로직이 동작한다.
  • by lazy는 기본적으로 멀티 스레드 환경에서도 안전하게 동작하도록 설계되었다.
    • 내부에서 스레드에 대한 동기화 처리를 해준다.
    • 기본 모드는 LazyThreadSafetyMode.SYNCHRONIZED이다.
    • LazyThreadSafetyMode.NONE일 경우 실행될 때마다 결과가 변경될 수 있다(스레드에 안전하지 못하지만, 오버헤드가 적다)
    • 멀티 스레드 환경 아닌 경우 NONE 모드를, 멀티 스레드 환경 이어도 동기화가 필요하지 않은 경우라면, PUBLICATION 모드를 멀티 스레드 환경에 동기화가 필요한 경우라면 SYNCHRONIZED 모드를 사용하자

lateinit

가변 프로퍼티에 대한 지연 초기화가 필요한 경우, 특정 프레임워크나 라이브러리에서 지연 초기화가 필요한 경우 lateinit을 사용해 해결하자

class ch7_lateExample {

    lateinit var text: String //가변 프로퍼티의 지연 로딩
    //불변으로 만들면 컴파일 오류가 발생한다.//nullable(?)하게 선언해도 컴파일 오류가 발생한다.

    val textInitialized: Boolean
        get() = this::text.isInitialized

    fun printText() {
        /*
        if (this::text.isInitialized) { //단, 이함수는 클래스 외부에서 사용이 안된다.
            println("초기화됨")
        } else {
            text = "안녕하세요" // 지연 초기화
        }
        text = "안녕하세요"
         */
        println(text) //출력을 먼저 하고 초기화 하면 예외 발생(컴파일 오류가 발생하지 않고 런타임 오류가 발생
    }
}

fun main() {
    val test = `ch7_lateExample`()
    //test.printText()
    //test.printText()

    /*
    test.text="하이요"
    //test.text.isInitialized //존재하지 않는다
    test.printText()
     */
    if(!test.textInitialized){
        test.text = "하이요"
    }
    test.printText()
}
  • lateinit은 가변 변수여야 한다(var)
    • val 인 경우 컴파일 오류가 발생하며, lateinit을 이용한 경우엔 항상 non-null이다
  • 초기화 이전에 사용하면, 오류가 발생한다(특정 DI와 같이 외부에서 초기화를 해주는 경우를 염두에 두고 만든 기능이기에 초기화 전에 사용하더라도 컴파일 오류는 발생하지 않는다)
    • 초기화 여부를 파악하고 사용하려면 isInitialized 프로퍼티를 사용하자
    • isInitialized는 내부에선 사용 가능 하지만, 외부에선 사용할 수 없다
728x90

'실무 프로젝트로 배우는 Kotlin & Spring > 코틀린 고급' 카테고리의 다른 글

스코프 함수  (0) 2022.10.22
페어와 구조분해할당  (0) 2022.10.16
제네릭  (0) 2022.10.16
확장 함수  (0) 2022.10.09
실드 클래스  (0) 2022.10.09