Kotlin 지연 초기화(lazy initialization - lateinit, lazy)
변수를 초기화를 해야하는데 생성자에서 할수 없는데 null이 가능한 변수로 만들고 싶지 않은경우
객체는 생성했지만 나중에 필요한 경우
여러가지 이유로 초기화를 늦게 해야하는 일이 발생한다.
늦게 초기화 하기위해서 lazy inialization을 사용하게 된다.
https://play.google.com/store/apps/details?id=com.danchoo.tagalbum&hl=ko
1. lateinit 키워드 사용
Toy Project중 일부 코드를 가지고 왔다.
private lateinit var adapter: BucketListAdapter
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_bucket_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initRecycler()
}
private fun initRecycler() {
recycler.layoutManager = LinearLayoutManager(context)
adapter = BucketListAdapter()
adapter.listener = object : BucketListAdapter.BucketListListener {
override fun onClickItem(item: BucketItem, position: Int) {
}
}
recycler.adapter = adapter
}
private lateinit var adapter: BuketListAdapter
lateinit 키워드를 사용하여 RycyclerView의 adapter를 변수를 선언했다.
lateinit 키워드를 사용하면 무조건 var로 변수를 선언해야 한다.
val은 내부에 final키워드가 포함이 되어있기 때문에 생성자 이외에 다른곳에서 초기화 할수 없다.
그렇기 때문에 var로 변수를 선언한다.
https://smartstore.naver.com/happysiso
null이 될수 있는 변수로 변수를 선언 했을때 사용할때마다 !! 연산자를 써야한다.
null이 될수 없는 변수로 만들기 위해서 lateinit 키워드를 사용해서 adapter를 선언한다.
lateinit 키워드를 사용한 후에 초기화를 하지 않고 변수를 사용하면 아래와 같은 로그가 발생한다.
kotlin.UninitializedPropertyAccessException: lateinit property adapter has not been initialized
lateinit property를 사용했지만 초기화하지 않았다고 fatal로그가 남는다.
lateinit 키워드를 사용하여 변수를 선언한경우 kotlin이 자동으로 생성해주는 getter / setter 를 사용하지 못하고 custom하지도 못한다.
custom getter / setter를 만드려고 하면 에러가 발생한다.
'lateinit' modifier is not allowed on properties with a custom getter or setter
2. lazy 함수 사용
이번에는 같은 코드로 lazy 함수를 사용해서 초기화를 했다.
private val adapter: BucketListAdapter by lazy {
BucketListAdapter()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_bucket_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initRecycler()
}
private fun initRecycler() {
recycler.layoutManager = LinearLayoutManager(context)
adapter.listener = object : BucketListAdapter.BucketListListener {
override fun onClickItem(item: BucketItem, position: Int) {
}
}
recycler.adapter = adapter
}
private val adapter: BucketListAdapter by lazy {
BucketListAdapter()
}
lazy 함수를 사용했다.
lazy 함수를 사용했을 때 변수는 반드시 val로 선언해야 한다.
여기서 by 키워드가 있는데 해당 키워드는 위임 프로퍼티 (delegate property)를 만들때 사용하는 키워드이다.
해당 키워드는 다른 포스팅때 자세하게 다루도록 하겠다.
lazy 함수에 객체 생성을 위임하겠다는 의미이다.
lazy 함수는 getValue메소드가 들어있는 객체를 반환한다.
/**
* Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
* and the default thread-safety mode [LazyThreadSafetyMode.SYNCHRONIZED].
*
* If the initialization of a value throws an exception, it will attempt to reinitialize the value at next access.
*
* Note that the returned instance uses itself to synchronize on. Do not synchronize from external code on
* the returned instance as it may cause accidental deadlock. Also this behavior can be changed in the future.
*/
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
/**
* Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
* and thread-safety [mode].
*
* If the initialization of a value throws an exception, it will attempt to reinitialize the value at next access.
*
* Note that when the [LazyThreadSafetyMode.SYNCHRONIZED] mode is specified the returned instance uses itself
* to synchronize on. Do not synchronize from external code on the returned instance as it may cause accidental deadlock.
* Also this behavior can be changed in the future.
*/
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
/**
* Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
* and the default thread-safety mode [LazyThreadSafetyMode.SYNCHRONIZED].
*
* If the initialization of a value throws an exception, it will attempt to reinitialize the value at next access.
*
* The returned instance uses the specified [lock] object to synchronize on.
* When the [lock] is not specified the instance uses itself to synchronize on,
* in this case do not synchronize from external code on the returned instance as it may cause accidental deadlock.
* Also this behavior can be changed in the future.
*/
public actual fun <T> lazy(lock: Any?, initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer, lock)
lazy 함수를 사용해서 초기화한 변수는 custom getter / setter를 가질수 없다.
기본으로 생성되는 getter는 사용할 수 있다. 하지만 val으로 선언했기 때문에 setter는 사용하지 못한다.
getter를 Custom하려고 하면 에러가 발생한다.
Delegated property cannot have accessors with non-default implementations
(위임 된 속성은 기본 구현이 아닌 접근자를 가질 수 없습니다)
Kotlin In Action 에 아래와같이 나와있다.
lazy함수는 기본적으로 쓰레드에 안전하다.
필요에 따라 동기화에 사용할 락을 lazy 함수에 전달할수 있다.
다중 스레드 환경에서 사용하지 않는 프로퍼티를 위해 lazy함수가 동기화를 하지 못하게 막을 수 있다.
'Android > Kotlin' 카테고리의 다른 글
[Android] Compose State, Statefull, Stateless, State Hoisting (0) | 2022.03.26 |
---|---|
[Android] Compose 와 선언형 UI (0) | 2022.03.26 |
[Android] Kotlin Object keyword (singleton, companion, anonymous inner class) (0) | 2019.09.01 |
[Android] Kotlin Property (프로퍼티) (0) | 2019.08.23 |
[Android] Kotlin 시작하기 (0) | 2019.08.15 |
댓글