안드로이드 개발하는 kancho입니다.
이번 포스팅에서는 코틀린의 Lazy 지연 초기화에 대해 알아보고자 합니다.
lazy 초기화는 안드로이드 개발에 있어 중요한 개념이며 자주 사용하기도 합니다.
User 클래스를 아래와 같이 정의해보자.
생성자 파라미터와 email 프로퍼티는 User 인스턴스 생성 시 메모리에 올라가게 된다.
이후 변수에 저장된 값을 빠르게 불러올 수 있게 된다.
아래 코드에서
User 인스턴스를 만들고 getFullName, getUserInfo 함수를 호출하면 순서대로 println()을 호출하게 된다.
/*
firstName, lastName -> 생성자 프로퍼티
email -> 프로퍼티
getFullName() -> User의 전체 이름
getUserInfo() -> User의 전체 정보
*/
class User(private val firstName: String, private val lastName: String) {
private var email: String? = "kancho@tistory.com"
init {
println("init")
}
fun getFullName(): String {
println("fullName")
return "$firstName $lastName"
}
fun getUserInfo(): String {
println("userInfo")
return "$firstName $lastName $email"
}
}
fun main() {
val user = User("kancho", "park")
println(user.getFullName())
println(user.getUserInfo())
}
/*
result ->
init
fullName
kancho park
userInfo
kancho park kancho@tistory.com
*/
email 변수를 보면 getUserInfo 함수를 호출할 때만 사용된다.
만약 다른 User 인스턴스는 getUserInfo 함수를 사용하지 않는다면 어떻게 될까?
email 프로퍼티는 메모리에 올라가 있지만 사용하고 있지 않는 상태가 될 것이다.
이는 메모리 낭비가 될 수 있다.
그렇다면 email 변수를 사용하는 경우에 초기화시킬 수 있는 방법은 없을까?
Lazy 지연 초기화를 사용하면 된다.
Lazy는 어떠한 property를 처음 읽을 때까지 값에 대한 초기화를 미뤄두고 싶을 때 사용한다.
아래 코드는 email 변수에 lazy 초기화를 사용한 것이다.
class User(private val firstName: String, private val lastName: String) {
private val email by lazy {
println("email initialized")
"kancho@tistory.com"
}
init {
println("init")
}
fun getFullName(): String {
println("fullName")
return "$firstName $lastName"
}
fun getUserInfo(): String {
println("userInfo")
return "$firstName $lastName $email"
}
}
fun main() {
val user = User("kancho", "park")
println(user.getFullName())
println("------------------")
println(user.getUserInfo())
}
/* result ->
init
fullName
kancho park
------------------
userInfo
email initialized
kancho park kancho@tistory.com
*/
getUserInfo 함수를 호출해서 email 프로퍼티를 사용하기 전까지 email 프로퍼티는 초기화되지 않는 것을 볼 수 있다.
email 프로퍼티는 email을 부르는 순간에 초기화된다.
lazy 초기화를 사용하게 되면 필요에 따라 property를 메모리에 올릴 수 있기 때문에 메모리 낭비를 줄이며 개발할 수 있다.
Lazy 특징
- immutable에서만 사용 (val)
- 호출하는 시점에 by lazy에 의해 초기화 진행
- 기본적으로 synchronized로 동작
- primitive(원시) type에도 사용 가능
- thread-safe (스레드 안전)
- 지역 변수에도 사용 가능
- smart cast 불가능
lazy는 어떤 방식으로 동작할까?
lazy 함수는 아래와 같이 정의되어 있다.
lazy의 매개변수로 initializer 식을 받고 SynchronizedLazyImpl를 호출해 받은 initializer를 생성자로 넣어준다
public actual fun <T> lazy(initializer: () -> T): Lazy<T> =
SynchronizedLazyImpl(initializer)
SynchronizedLazyImpl 클래스를 보면 initializer와 _value 프로퍼티를 볼 수 있다.
initializer 프로퍼티는 생성자로 받은 initializer로 초기화하고
_value는 UNINITIALIZED_VALUE로 초기화하고 있다.
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)
}
SynchronizedLazyImpl 이 동작하는 순서
값을 호출하면 get을 통해 value 호출이 일어난다
- 처음 호출 시 _v1의 값은 UNINITIALIZED_VALUE 이기에 return synchronized(lock) 블록으로 넘어간다
- synchronized(lock) 블록에서 _v2는 UNINITIALIZED_VALUE 이기에 else 가지로 넘어간다
- _value 값에 초기화된 initializer 식이 할당된다
- 다시 lazy 프로퍼티를 호출하면 get을 통해 value 호출이 일어난다
- _v1에 생성된 _value 값을 넣어주고 UNINITIALIZED_VALUE가 아니기에 _v1의 값을 리턴한다
즉, 처음 초기화할 때 값을 저장하고 이후에는 저장된 값을 읽어오게 되는 것이다.
또한, synchronized 호출로 인해 thread-safe 한 것을 볼 수 있다.
lazy는 디폴트로 synchronized thread-safety mode를 사용한다.
만약 다른 모드를 사용하고 싶다면 LazyThreadSafetyMode 클래스를 참고해서 아래 코드와 같이 사용하면 된다.
SYNCHRONIZED, PUBLICATION, NONE 3가지 모드가 있다.
// NONE, SYNCHRONIZED, PUBLICATION
private val email by lazy(LazyThreadSafetyMode.NONE) {
println("email initialized")
"kancho@tistory.com"
}
'코틀린' 카테고리의 다른 글
[코틀린] Scope function (영역 함수) (0) | 2022.10.10 |
---|---|
[코틀린] 확장 (Extension) (0) | 2022.10.10 |
코틀린 정리 - (3) (0) | 2022.10.03 |
코틀린 정리 - (2) (0) | 2022.09.30 |
코틀린 정리 - (1) (0) | 2022.09.18 |