평소에 안드로이드 프로젝트를 진행하면서 종종 뜨는 오류가 있었다.
kotlin.UninitializedPropertyAccessException: lateinit property adapter has not been initialized
바로 지연 초기화를 제대로 사용하지 않았을 때 발생하는 오류인데, 이번 기회를 통해 제대로 지연초기화에 대해 알아보고자 한다.
기본적으로 lazy 의 사전적 의미는 게으른
, 느긋한
, 여유로운
을 의미한다. 한마디로 위의 뜻을 직역하면 게으른 초기화
라는 의미인데, 게으른 초기화
는 프로그래밍에서 꽤 긍정적인 의미로 쓰인다.
왜 지연 초기화를 사용할까?
지연 초기화라는 이름만 보아도 알 수 있듯이, 초기화 작업을 극한으로 미루다가 사용자가 필요로 할 때 진행하는데, 이 방법을 사용함으로서 메모리 낭비를 줄일 수 있다는 장점이 있다. 그리고 이는 퍼포먼스의 향상으로 이어진다.
또한 프로그램을 만들다보면 (val age: Int)
와 같은 변수를 선언하였지만 객체의 정확한 값을 뒤에 가서야 알 수 있는 경우 null
로 초기화할 수도 없어 굉장히 난감해진다. 이럴 때 지연 초기화를 사용함으로써 문제를 극복할 수 있다.
코틀린에서의 지연 초기화는 lateinit
을 사용하는 방법과 lazy
를 사용하는 방법이 있다. 이 두 키워드를 천천히 알아보도록 하자.
var
로 선언된 프로퍼티만 사용 가능하다.getter
, setter
를 사용할 수 없다.생각보다 조건이 많지만, 막상 실제 코딩을 하다보면 상당히 많이 쓰이는 키워드 중 하나이다. 실제로 코드를 보며 어떤 때에 사용하는지 알아보도록 하자.
class Hoya {
lateinit var nickname : String // 지연 초기화 선언
// var nickname : String // 프로퍼티 자료형은 초기화를 해야하므로 오류
fun test() {
if(::nickname.isInitialized) { // 초기화 여부 판단
println("초기화 되었음.")
}
}
}
fun main() {
val hoya = Hoya()
hoya.test() // 초기화 되지 않은 시점
// println("nickname = ${hoya.nickname}") // 이 시점에서 사용하면 오류 발생
hoya.nickname = "hoyaho" // 이 시점에서 초기화
hoya.test() // 초기화가 됐으므로 초기화 되었다고 알림
println("nickname = ${hoya.nickname}")
}
주석을 읽으면 자연스럽게 이해가 될 것이다. 주의할 점으로, 초기화가 되지 않았는데 변수에 접근하면 오류가 발생하므로 주의해야 한다.
val
에서만 사용이 가능하다.class HelloLazy {
init {
println("init block") // 최초 초기화 선언을 알림
}
val subject : String by lazy { "Lazy Test" }
fun flow() {
println("초기화 되지 않았음") // 아직 초기화 되지 않음
println("subject one : $subject") // 최초 초기화 시점
println("subject two : $subject") // 초기화된 값 사용 (불변)
}
}
fun main() {
val test = HelloLazy()
test.flow()
}
최초 접근 시점에서 초기화하고 사용하는 것이 lazy
키워드의 핵심이라고 볼 수 있다. 또, by lazy
에는 동기화와 관련해 여러 모드가 제공된다.
🖐 단일 스레드만 사용하는 것을 보장한다는게 어떤 의미인데?
만약, 한 변수에 여러 스레드가 접근한다면 어떻게 될지 생각해보면 간단하다. Thread1 이 하나의 변수를 건드리고 있는데, 갑자기 Thread2가 그 변수를 건드린다면? 값의 일관성을 보장할 수 없을 것이다. 즉, Thread-safe
를 보장하는 것이다.
val lazyValue by lazy(LazyThreadSafetyMode.NONE) {
// Initialize code block
}
동기화가 필요하지 않은 환경에서 코드를 실행한다면, NONE
모드를 사용하여 성능을 높일 수 있을 것이다. 상황에 맞게 적절한 모드를 사용하도록 하자.
🥸 개인적으로 publication에 대해 조금 헷갈렸는데, 쉽게 이야기해 여러 스레드가 변수에 접근하는 것은 가능하지만 최초 접근한 스레드가 값을 초기화했다면 그 값만을 사용할 수 있다고 보면 된다.
참고 및 출처