-
[Kotlin] by lazyKotlin/늦은 초기화 2021. 12. 12. 15:37
- by 키워드 용도
1. 클래스(인터페이스 메서드 구현) 위임
2. 프로퍼티(accesor) 위임
- by lazy - 프로퍼티 위임의 한 종류
코틀린은 기본 라이브러리를 통해 프로퍼티를 위임하는 여러 방법들을 제공한다. 그 중 한 종류이다.
val 프로퍼티에만 사용가능하다.
lazy 코드를 이해하기 위해 프로퍼티의 custom getter의 동작을 보자
person2의 custom getter = get(){ //코드 }
//코드는 person2를 참조할 때(getter사용시) 호출된다.
인스턴스만 생성하고 getter를 호출하지 않아 getter의 println은 출력되지 않는다.
프로퍼티를 참조(getter 호출할떄) println코드가 실행된다는 것을 인지하고 lazy의 동작 과정을 살펴보자.
클래스 멤버 프로퍼티 person 의 getter 위임 과정 확인
프로퍼티 위임에 해당하는 프로퍼티 by 객체 -> 객체가 프로퍼티의 accesor 동작을 대신한다 (by를 통한 프로퍼티 위임)
lazy는 고차함수로 Lazy인터페이스를 구현한 어떠한 객체를 리턴한다. 이 객체가 person의 getter를 대신하게 될 것이다.
- lazy 고차함수
package kotlin (LazyJVM.kt 파일)
by lazy{ 람다식 } 람다식의 코드 내용을 들고있는 함수가 initializer이다.
T타입은 Person이 된다.
Lazy<Person>를 구현한 SynchronizedLazyIml(initializer) 객체가 person 프로퍼티의 getter동작을 대신할 것이다.
- SynchronizedLazyImpl 객체 생성 과정을 살펴보기 전에 Lazy<T>인터페이스를 살펴보자.
값을 저장할 value와 , value의 값이 초기화되어있는 상태인지 확인하는 isInitialized 메서드
- SynchronizedLazyImpl 객체 생성 과정 확인
pacakage kotlin (Lazy.kt 파일)
package kotlin (LazyJVM.kt 파일)
코틀린에서는 자바와 다르게 private 클래스는 같은 파일에서 접근 가능하다.
lazy고차함수와 같은 파일에 들어있어 lazy고차함수가 클래스를 사용할 수 있다.
SynchronizedLazyImlp의 value 프로퍼티의 custom getter인 get() 을 살펴보면 getter가 첫 호출시에는 처음에는 빨간색 사각형의 코드들이 수행된다. 해당 부분에서 val typedValue = initializaer!!() <- 우리가 작성한 코드들에 해당하는 함수가 호출된다.
typedValue= Person객체
_value = typedValue <- 추후 person의 getter가 재호출될 때 전달해주기 위해 여기다가 기록해 놓는다.
추후 value의 getter가 호출될 때 우리가 작성한 함수가 다시 실행되지 않게 함수 변수(initalizer)를 null로 만들어버린다.
여기까지가 value프로퍼티가 처음 불릴 때 수행되는 코드들이다.
value프로퍼티가 두번째 호출될 때부터는 val v1=_value <-기록된 값을 return v1 as Person 으로 형변환하여 내준다.
바로 전까지 설명한 내용들은 value의 get(){ .. }으로 실제로는 value프로퍼티를 참조할 때마다 수행되는 코드들이다.
그렇기 때문에 글의 초반에서 다룬 custom getter동작에 따라 SynchronizedLazyImpl 인스턴스가 생성되었어도 value를 한번이라도 참조하지 않으면 코드가 수행되지 않는다.
바이트 코드
person프로퍼티의 accesor(getter)동작을 대신하기 위할 인스턴스를 저장하기 위해 TestClass에 Lazy멤버변수가 포함됨
TestClass의 생성자에서 lazy메서드가 호출되어 리턴된 SynchronizedImpl 인스턴스가 person$delegate에 대입된다.
TestClass인스턴스 생성이 완료되었다.
person의 getter는 내부적으로 SynchronizedImpl인스턴스의 value getter를 호출한다.
person getter가 한번도 호출되지 않으면 SynchronizedImpl인스턴스의 value getter또한 한번도 호출되지 않아
우리의 람다식 코드가 실행되지 않는다.
즉 person프로퍼티를 처음으로 참조될 때 우리가 작성한 코드들에 해당하는 함수가 수행되고, 두번째 참조부터 _value에 기록된 값을 전달해준다.
person getter호출이 없어 아직 람다식 코드가 호출되지 않음
person getter호출시 Lazy구현 인터페이스의 value프로퍼티의 getter(getValue)가 호출되어 람다식 코드가 호출되고 초기화가 진행된다.
Lazy의 isInitialized 메서드는 다음과 같은 방식으로 사용가능하다.
여러 스레드에서의 testClass.person 프로퍼티 동시에 접근
우리가 구현한 lazy메서드
SynchronizedLazyImpl()객체를 lock으로 사용하고 특별히 제한된 것이 없으므로 모든 스레드에서 접근 가능하다.
여러 스레드에서 동시에 person프로퍼티에 접근하는 경우
먼저 접근한 스레드가 lock을 획득하고 람다식을 호출하여 값을 초기화한다. 먼저 접근한 스레드가 _value 값 초기화를 마치고 lock을 반납하여 나중에 접근한 스레드가 lock을 획득하여 val _v2 = 초기화된 _value를 통해 값을 읽는다.
모든 스레드가 값을 초기화시킬 수 있고, 가장 먼저 synchronized블록에 들어간 스레드가 값을 초기화한다.
모드 SYNCHRONIZED는 lock을 사용하는 모드. 이외의 두 모드는 lock을 사용하지 않는다.
가장 정확하게 사용하려면 SYNCHRONIZED 모드를 사용하자.
PUBLICATION 모드
NONE 모드
'Kotlin > 늦은 초기화' 카테고리의 다른 글
[Kotlin] by 키워드 (0) 2021.09.03