일반적으로 propertie
를 non-null type
으로 선언하기 위해선 생성자에서 초기화가 필요하다. 하지만 생성자에서 초기할 수 없거나, null type
을 피하고 싶은 경우, 사용 시점에 초기화하고 싶은 경우를 위해 지연초기화(Late initialization, Lazy initialization)를 제공한다.
lateinit var string: String
초기화되어 있는지 확인하려면 property
앞에 ::
키워드를 사용해 .isInitialized
를 호출한다.
class LateInitTest {
latinit var string: String
@Test
fun test() {
// println(string) // Exception
if (!::string.isInitialized) {
string = "init"
}
println(string)
}
}
val string: String by lazy { "init" }
class LazyTest {
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
@Test
fun test() {
println(lazyValue)
println(lazyValue)
}
}
computed!
Hello
Hello
위의 출력 결과를 살펴보면 해당 프로퍼티 최초 접근 시 람다식이 호출되며 그 이후에는 해당 람다식의 결과값을 return 한다.
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
private fun writeReplace(): Any = InitializedLazyImpl(value)
}
내부 코드를 살펴보면 최초 _value
는 UNINITIALIZED_VALUE(internal object UNINITIALIZED_VALUE
)로 초기화된다.
get()이 호출될 때 _value
가 UNINITIALIZED_VALUE가 아니면 _value
를 return 한다.
그 이후에는 synchronized로 thread safety 하게 동작하며 다시 한번 _value
가 UNINITIALIZED_VALUE 인지 체크하며 UNINITIALIZED_VALUE인 경우 람다식을 호출하며 그 결과값을 저장한 뒤 return 한다.
lateinit
은 생성자에선 초기화할 수 없지만 non-null
하게 사용하고 싶은 경우, 특정 시점에 초기화하며 value
를 지속적으로 변경해야 할 경우 사용한다.
lazy
는 특정 시점에 초기화하고 싶은 경우, 최초 접근 시 value
를 초기화하며 이후 value
의 변경이 필요하지 않은 경우 사용한다.