Kotlin의 지연 초기화 원리 분석: lateinit var 및 by lazy

12162 단어
Koltin의 속성은 선언과 동시에 초기화되어야 합니다. 그렇지 않으면 오류가 발생합니다.예를 들면 다음과 같습니다.
private var name0: String //  
private var name1: String = "xiaoming" //   
private var name2: String? = null //   

  그러나 때때로 나는 비울 수 있는 유형의 대상을 설명하고 싶지 않다. 게다가 대상이 설명할 때 초기화할 수도 없다. 그러면 Kotlin이 제공하는 지연 초기화를 사용해야 한다.Kotlin은 초기화를 지연하는 두 가지 방법이 있습니다.하나는 lateinit var, 하나는 by lazy.

lateinit var

private lateinit var name: String

  lateinit var은 클래스 속성만 수식할 수 있고 국부 변수는 수식할 수 없고 대상만 수식할 수 있으며 기본 형식은 수식할 수 없습니다. (기본 형식의 속성은 클래스가 불러온 후 준비 단계에서 기본값으로 초기화되기 때문입니다.)  lateinit var의 역할도 간단합니다. 즉, 컴파일러가 검사할 때 속성 변수가 초기화되지 않았기 때문에 오류를 보고하지 않도록 합니다.  Kotlin은 개발자가 lateinit var 키워드를 명시적으로 사용할 때 반드시 뒤에 있는 합리적인 시기에 이 속성 대상을 초기화할 것이라고 믿습니다. (그러나 누가 알았겠는가. 아마도 그가 다 쓰고 나서야 초기화하지 않은 것을 생각할 것입니다.)

by lazy


  by lazy 자체는 속성 의뢰입니다.속성 의뢰의 키워드는 by입니다.by lazy는 다음과 같이 씁니다.
//         
val name: Int by lazy { 1 }

//           
public fun foo() {
    val bar by lazy { "hello" }
    println(bar)
}

  이하name 속성을 대표로bykazy의 원리를 설명하고 국부 변수의 초기화도 같은 원리이다.  by lazy는 속성 성명 val, 즉 불가변 변수를 요구하며 자바에서 final 수식에 해당한다.  이것은 이 변수가 초기화되면 더 이상 값을 수정할 수 없다는 것을 의미합니다. (기본 형식은 값을 수정할 수 없고 대상 형식은 인용을 수정할 수 없습니다.){}내의 조작은 유일하게 초기화된 결과를 되돌려주는 것이다.  by lazy는 클래스 속성이나 국부 변수에 사용할 수 있습니다.
가장 간단한 코드 분석 by lazy의 실현:
class TestCase {

   private val name: Int by lazy { 1 }

   fun printname() {
       println(name)
   }

}

IDEA에서 toolbar의 Tools -> Kotlin -> Show Kotlin ByteCode를 클릭하여 편집기 오른쪽에 있는 도구막대를 확인합니다.
바이트 분석을 보고 싶지 않은 것은 바로 건너뛸 수 있습니다. 각 바이트 뒤에 자바/kotlin 버전의 해석이 있습니다.
보다 완전한 바이트 코드 세그먼트는 다음과 같습니다.
public ()V
 L0
  LINENUMBER 3 L0
  ALOAD 0
  INVOKESPECIAL java/lang/Object. ()V
 L1
  LINENUMBER 5 L1
  ALOAD 0
  GETSTATIC com/rhythm7/bylazy/TestCase$name$2.INSTANCE : Lcom/rhythm7/bylazy/TestCase$name$2;
  CHECKCAST kotlin/jvm/functions/Function0
  INVOKESTATIC kotlin/LazyKt.lazy (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy;
  PUTname com/rhythm7/bylazy/TestCase.name$delegate : Lkotlin/Lazy;
  RETURN
 L2
  LOCALVARIABLE this Lcom/rhythm7/bylazy/TestCase; L0 L2 0
  MAXSTACK = 2
  MAXLOCALS = 1

  이 단락 코드는 바이트 코드로 생성된 public ()V 방법 안에 있습니다.이 방법에서 단례object가 아닌 Kotlin류의 속성 초기화 코드 문장은 컴파일러 처리를 거친 후 이 방법에 수집되고object 대상이라면 대응하는 속성 초기화 코드 문장은 static ()V 방법에 수집되기 때문이다.또한 바이트 코드에서 이 두 방법은 서로 다른 방법으로 서명한 것으로 언어 단계에서 두 방법이 같은지 아닌지를 판단하는 방식과 다르다.전자는 실례 구조 방법이고 후자는 유형 구조 방법이다.  L0과 L1 사이의 바이트 코드는 Object () 를 호출하는 구조 방법을 나타냅니다. 이것은 기본적인 부류 구조 방법입니다.L2 다음은 로컬 변수 테이블 설명입니다.L1과 L2 사이의 바이트는 다음과 같은 kotlin 코드에 해당합니다.
 private val name: Int by lazy { 1 }

L1과 L2 사이의 이 바이트 코드는 소스 코드 줄 번호 5가 바이트 코드에 대응하는 방법의 체내 줄 번호 1을 의미한다.this(비정상적인 방법의 기본 첫 번째 로컬 변수)를 창고 꼭대기로 밀어넣기;정적 변수 가져오기com.rhythm7.bylazy.TestCase$name$2.INSTANCE;INSTANCE가 kotlin.jvm.functions.Function0클래스로 변환할 수 있는지 확인하기;정적 방법kotlin.LazyKt.lazy(kotlin.jvm.functions.Function0)을 호출하여INSTANCE를 매개 변수로 전송하고 kotlin.Lazy 유형의 반환값을 획득한다.이상의 반환값을 com.rhythm7.bylazy.TestCase.name$delegate에 부여하기;마지막 마무리 방법.
자바 코드에 해당:
TestCase() {
    name$delegate = LazyKt.lazy((Function0)name$2.INSTANCE)
}

여기서 name$delegate는 컴파일하여 생성된 속성이며 객체 유형은 Lazy입니다.
  private final Lkotlin/Lazy; name$delegate  
name$2는 모두 컴파일하여 생성된 내부 클래스이다.
final class com/rhythm7/bylazy/TestCase$name$2 extends kotlin/jvm/internal/Lambda  implements kotlin/jvm/functions/Function0

  name$2은kotlin을 계승했다.jvm.internal.Lambda 클래스와 kotlin을 실현했습니다.jvm.functions.Function0 인터페이스에서 알 수 있듯이 name$2는kotlin 함수 매개 변수 유형()->T의 구체적인 실현이고 바이트 코드 분석을 통해name$2.INSTANCE는 이 실현 클래스의 정적 대상 실례이다.따라서 위 바이트는 Koltin의
init {
    name$delegate = lazy(()->{})
}

  그러나 이러한 코드의 역할은 컴파일러에 생성된 속성 변수에 값을 부여하는 것일 뿐 다른 조작은 없다.  진정으로 속성 변수의 초기화를 실현하는 곳은 속성name의 Getter 방법입니다.  자바 코드에서 kotlin 코드를 호출한 적이 있다면 자바 코드에서 setter나 Getter로만 koltin이 작성한 대상 속성에 접근할 수 있습니다. 이것은 kotlin에서 기본적으로 속성에 private 수식자를 추가하고 이 속성 변수가 val인지 var인지에 따라 Geter나 Getter가 setter와 함께 생성되기 때문입니다.그리고 이 속성에 대한 접근 권한에 따라 Getter와setter에 대응하는 접근 권한 수식자를 추가합니다. (기본값은public입니다.)
getName()의 구현을 보려면 다음과 같이 하십시오.
private final getName()I
 L0
  ALOAD 0
  GETFIELD com/rhythm7/bylazy/TestCase.name$delegate : Lkotlin/Lazy;
  ASTORE 1
  ALOAD 0
  ASTORE 2
  GETSTATIC com/rhythm7/bylazy/TestCase.$$delegatedProperties : [Lkotlin/reflect/KProperty;
  ICONST_0
  AALOAD
  ASTORE 3
 L1
  ALOAD 1
  INVOKEINTERFACE kotlin/Lazy.getValue ()Ljava/lang/Object;
 L2
  CHECKCAST java/lang/Number
  INVOKEVIRTUAL java/lang/Number.intValue ()I
  IRETURN
 L3
  LOCALVARIABLE this Lcom/rhythm7/bylazy/TestCase; L0 L3 0
  MAXSTACK = 2
  MAXLOCALS = 4

자바 코드에 해당:
private final int getName(){
    Lazy var1 = this.name$delegate;
    KProperty var2 = this.$$delegatedProperties[0]
    return ((Number)var1.getValue()).intValue()
}

 name의 Getter 방법은 사실 되돌아온 방법입니다. name$delegate.getValue()$$delegatedProperties는 컴파일한 후 자동으로 생성되는 속성이지만 여기에는 사용되지 않기 때문에 신경 쓸 필요가 없다.
   그렇다면 이제 우리가 관심을 가져야 할 것은 name$delegate.getValue(), 즉 레이지류getValue() 방법의 구체적인 실현뿐이다.
Lazy Kt를 먼저 봅시다.lazy(()->T)의 구현:
public fun  lazy(initializer: () -> T): Lazy = SynchronizedLazyImpl(initializer)

다시 보면 SynchronizedLazyImpl류의 구체적인 실현:
private object UNINITIALIZED_VALUE

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy, 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
                }
            }
        }

......
}

이상 코드의 읽기 난이도는 매우 낮다.Synchronized Lazy Impl은 Lazy 클래스를 계승하고 일반 형식을 지정한 다음 Lazy 부모 클래스의 getValue () 방법을 다시 썼습니다.getValue () 방법에서 _value 초기화 여부를 판단하고 _value 되돌아와value의 초기화 지연 작용을 실현합니다. value의 초기화 행위 자체가 안전하다는 것을 주의하십시오.

총결산


요약하자면 속성name에 by lazy가 필요할 때 어떻게 이루어졌는지:
  • 이 속성의 추가 속성을 생성합니다:name$delegate;
  • 구조기에서 레이지((()->T)를 사용하여 만든 레이지 실례 대상을name$delegate에 부여하기;
  • 이 속성이 호출되었을 때, Getter 방법이 호출되었을 때name$delegate를 되돌려줍니다.getVaule(), name$delegate.getVaule () 방법의 반환 결과는 대상name$$delegate 내부의value 속성 값, getVaule ()이 처음 호출될 때value를 초기화하여 다음에는 바로value의 값을 되돌려 속성 값의 유일한 초기화를 실현합니다.

  • 그럼 다시 한 번 요약하자면,lateinit var과by lazy 중 어느 것이 더 좋아요?   우선 두 사람의 응용 장면은 약간 다르다.  그리고 둘 다 속성 초기화 시간을 늦출 수 있지만lateinit var은 컴파일러로 하여금 속성이 초기화되지 않은 검사를 무시하게 하고 다음에 어디에서 언제 초기화할지 개발자가 스스로 결정해야 한다.by lazy는 진정으로 성명을 하는 동시에 초기화를 지연할 때의 행동을 지정합니다. 속성이 처음 사용될 때 자동으로 초기화됩니다.그러나 이런 기능들은 이를 위해 잃어버린 대가를 치러야 한다.

    좋은 웹페이지 즐겨찾기