Kotlin- "지연에 의한"대 "lateinit"를 사용한 속성 초기화


279

Kotlin에서는 생성자 내부 또는 클래스 본문 상단에서 클래스 속성을 초기화하지 않으려는 경우 기본적으로 언어 참조의 두 가지 옵션이 있습니다.

  1. 게으른 초기화

lazy ()는 람다를 가져오고 lazy 속성을 구현하기위한 대리자로 사용할 수있는 Lazy 인스턴스를 반환하는 함수입니다. get ()에 대한 첫 번째 호출은 lazy ()에 전달 된 람다를 실행하고 결과, 후속 호출을 기억합니다 get ()은 단순히 기억 된 결과를 반환합니다.

public class Hello {

   val myLazyString: String by lazy { "Hello" }

}

이에, 어디든지 첫 번째 통화와 subquential 전화, 그래서 myLazyString 돌아갑니다 "안녕하세요"

  1. 늦은 초기화

일반적으로 null이 아닌 유형으로 선언 된 속성은 생성자에서 초기화해야합니다. 그러나 이것은 종종 편리하지 않습니다. 예를 들어, 속성은 의존성 주입을 통해 또는 단위 테스트의 설정 방법으로 초기화 될 수 있습니다. 이 경우 생성자에 null이 아닌 이니셜 라이저를 제공 할 수 없지만 클래스 본문 내에서 속성을 참조 할 때 null 검사를 피하려고합니다.

이 경우를 처리하기 위해 lateinit 수정자를 사용하여 특성을 표시 할 수 있습니다.

public class MyTest {
   
   lateinit var subject: TestSubject

   @SetUp fun setup() { subject = TestSubject() }

   @Test fun test() { subject.method() }
}

수정자는 클래스 본문 내에 선언 된 var 속성 (기본 생성자는 아님) 및 속성에 사용자 지정 getter 또는 setter가없는 경우에만 사용할 수 있습니다. 특성의 유형은 널이 아니어야하며 기본 유형이 아니어야합니다.

따라서 두 옵션 모두에서 동일한 문제를 해결할 수 있기 때문에이 두 옵션 중에서 올바르게 선택하는 방법은 무엇입니까?

답변:


334

위임 된 속성 lateinit varby lazy { ... }위임 된 속성 의 중요한 차이점은 다음과 같습니다 .

  • lazy { ... }delegate는 val속성 lateinit에만 사용할 수있는 반면 var, final필드에 컴파일 할 수 없으므로 불변성을 보장 할 수 없으므로 s 에만 적용 할 수 있습니다.

  • lateinit var는 값을 저장하는 백킹 필드를 가지며, by lazy { ... }일단 계산 된 값이 저장되는 델리게이트 객체를 만들고 클래스 객체에 델리게이트 인스턴스에 대한 참조를 저장하고 델리게이트 인스턴스와 작동하는 속성에 대한 게터를 생성합니다. 따라서 클래스에 지원 필드가 필요하면 lateinit;

  • vals 외에도 lateinit널 입력 불가능 특성 및 Java 기본 유형에 사용할 수 없습니다 ( null초기화되지 않은 값 에 사용 되기 때문 ).

  • lateinit var예를 들어 프레임 워크 코드 내부에서 객체가 보이는 곳에서 초기화 할 수 있으며 단일 클래스의 다른 객체에 대해 여러 초기화 시나리오가 가능합니다. by lazy { ... }또한 속성에 대한 유일한 이니셜 라이저를 정의하며 서브 클래스에서 속성을 재정의하는 경우에만 변경할 수 있습니다. 미리 알 수없는 방식으로 외부에서 속성을 초기화하려면을 사용하십시오 lateinit.

  • 초기화 by lazy { ... }는 기본적으로 스레드로부터 안전하며 초기화 프로그램이 최대 한 번만 호출되도록 보장합니다 (그러나 다른 lazy과부하 를 사용하여 변경할 수 있음 ). 의 경우 lateinit var멀티 스레드 환경에서 속성을 올바르게 초기화하는 것은 사용자 코드에 달려 있습니다.

  • Lazy인스턴스가 저장 건네 심지어 여러 속성에 사용할 수 있습니다. 반대로 lateinit vars는 추가 런타임 상태를 저장하지 않습니다 ( null초기화되지 않은 값의 필드 에만 해당 ).

  • 당신의 인스턴스에 대한 참조를 보유하고 있다면 Lazy, isInitialized()당신이 이미 초기화되었는지 여부를 확인하기 위해 (그리고 당신이 할 수 있습니다 반사와 같은 예를 얻을 위임로부터 참조). lateinit 속성이 초기화되었는지 확인하려면 Kotlin 1.2부터 사용할property::isInitialized있습니다 .

  • 전달 된 람다 by lazy { ... }클로저에 사용되는 컨텍스트에서 참조를 캡처 할 수 있습니다 . 그러면 참조가 저장되고 속성이 초기화 된 후에 만 ​​참조가 해제됩니다. 이로 인해 Android 활동과 같은 객체 계층 구조가 너무 오랫동안 릴리스되지 않거나 속성에 액세스 할 수 있고 액세스 할 수없는 경우 이니셜 라이저 람다 내부에서 사용하는 것에주의해야합니다.

또한 질문에 언급되지 않은 또 다른 방법 Delegates.notNull()이 있습니다.이 방법은 Java 기본 유형의 속성을 포함하여 null이 아닌 속성의 지연된 초기화에 적합합니다.


9
좋은 답변입니다! lateinitKotlin과 Java에서 속성에 액세스하는 방식이 다르므로 setter의 가시성으로 백업 필드 를 노출시킵니다. Java 코드에서이 속성은 nullKotlin에서 확인하지 않고도 설정할 수 있습니다 . 따라서 lateinit게으른 초기화가 아니라 Kotlin 코드의 초기화가 아닙니다.
Michael

스위프트의 "!"에 해당하는 것이 있습니까? ?? 다시 말해서 초기화가 늦었지만 실패하지 않고 null인지 확인할 수 있습니다. 'theObject == null'을 확인하면 Kotlin의 'lateinit'가 "lateinit 속성 currentUser가 초기화되지 않았습니다"와 함께 실패합니다. 이는 핵심 사용 시나리오에서 null이 아니고 널이 아닌 추상화에 대해 코딩하려고하지만 예외 / 제한된 시나리오에서 null 인 경우 (예 : 현재 기록 된 액세스) 초기 로그인 / 로그인 화면을 제외하고 null이 아닌 사용자)
Marchy

@Marchy, 명시 적으로 저장된 Lazy+ .isInitialized()를 사용 하여 그렇게 할 수 있습니다 . 나는 null당신이 그것을 얻을 수 없다는 보장 때문에 그러한 속성을 확인하는 간단한 방법이 없다고 생각 null합니다. :) 이 데모를보십시오 .
핫키

@hotkey 너무 많이 사용 by lazy하면 빌드 시간이나 런타임이 느려질 수 있는 점이 있습니까?
Dr.jacky 's

나는 초기화되지 않은 가치 lateinitnull대한 사용 을 피하기 위해 사용 하는 아이디어를 좋아했습니다 . 그 이외의 다른 것을 null사용해서는 안되며 lateinitnull을 사용하면 제거 할 수 있습니다. 그것이 내가 Kotlin을 좋아하는 방법입니다 :)
KenIchi

26

또한 hotkey좋은 대답 을 위해 실제로 두 가지 중에서 선택하는 방법은 다음과 같습니다.

lateinit 외부 초기화 용 : 메소드를 호출하여 값을 초기화하기 위해 외부 물건이 필요할 때.

예를 들어

private lateinit var value: MyClass

fun init(externalProperties: Any) {
   value = somethingThatDependsOn(externalProperties)
}

lazy객체 내부의 종속성 만 사용 하는 동안 입니다.


1
외부 객체에 의존하더라도 초기화가 지연 될 수 있다고 생각합니다. 내부 변수에 값을 전달하면됩니다. 지연 초기화 중에 내부 변수를 사용하십시오. 그러나 Lateinit만큼 자연 스럽습니다.
Elye

이 접근법은 UninitializedPropertyAccessException을 던지고 값을 사용하기 전에 setter 함수를 호출하고 있는지 다시 확인했습니다. lateinit에 누락 된 특정 규칙이 있습니까? 귀하의 답변에서 MyClass와 Any를 android Context로 바꾸십시오.
Talha

24

매우 짧고 간결한 답변

lateinit : 최근에 null이 아닌 속성을 초기화합니다

지연 초기화와 달리 lateinit를 사용하면 컴파일러가 null이 아닌 속성 값이 생성자 단계에 저장되어 정상적으로 컴파일되지 않는다는 것을 인식 할 수 있습니다.

게으른 초기화

게으른 by 코 틀린 초기화 지연을 수행 하는 읽기 전용 (val) 속성을 구현할 때 매우 유용 할 수 있습니다 .

by lazy {...}는 선언 된 속성이 아니라 정의 된 속성이 처음 사용되는 초기화 프로그램을 수행합니다.


큰 대답, 특히 "선언이 아닌 정의 된 속성이 처음 사용되는 초기화 프로그램을 수행합니다"
user1489829

17

lateinit 대 게으른

  1. 늦게

    i) 가변 변수 [var]와 함께 사용

    lateinit var name: String       //Allowed
    lateinit val name: String       //Not Allowed

    ii) 널 입력 불가능 데이터 유형 만 허용

    lateinit var name: String       //Allowed
    lateinit var name: String?      //Not Allowed

    iii) 나중에 값을 초기화 할 것을 컴파일러에 약속합니다.

참고 : 초기화하지 않고 lateinit 변수 에 액세스하려고하면 UnInitializedPropertyAccessException이 발생합니다.

  1. 게으른

    i) 지연 초기화는 불필요한 객체 초기화를 방지하도록 설계되었습니다.

    ii) 변수를 사용하지 않으면 변수가 초기화되지 않습니다.

    iii) 한 번만 초기화됩니다. 다음에 사용할 때 캐시 메모리에서 값을 얻습니다.

    iv) 스레드 안전 (처음 사용 된 스레드에서 초기화됩니다. 다른 스레드는 캐시에 저장된 동일한 값을 사용합니다).

    v) 변수는 val 만 가능 .

    vi) 변수는 널 입력이 불가능 합니다.


7
게으른 변수는 var가 될 수 없다고 생각합니다.
Däñish Shärmà

4

모든 위대한 답변 외에도 게으른 로딩이라는 개념이 있습니다.

지연 로딩은 필요한 시점까지 객체의 초기화를 연기하기 위해 컴퓨터 프로그래밍에서 일반적으로 사용되는 디자인 패턴입니다.

올바르게 사용하면 응용 프로그램의 로딩 시간을 줄일 수 있습니다. 그리고 Kotlin의 구현 방식은 lazy()필요할 때마다 필요한 값을 변수에로드하는 것입니다.

그러나 lateinit는 변수가 null이거나 비어 있지 않다는 것을 확신하고 사용하기 전에 초기화됩니다 (예 : android의 onResume()메소드에서). 그래서 nullable 유형으로 선언하고 싶지 않습니다.


예, 나는 또한 초기화 onCreateView, onResume그리고 다른 lateinit,하지만 때로는 오류가 있습니다 (일부 이벤트 이전에 시작했기 때문에) 발생했습니다. 따라서 by lazy적절한 결과를 얻을 수 있습니다. lateinit수명주기 동안 변경 될 수있는 null이 아닌 변수에 사용 합니다.
CoolMind

2

위의 모든 것이 정확하지만 사실 간단한 설명 중 하나 LAZY ---- 처음 사용할 때까지 객체의 인스턴스 생성을 지연하려는 경우가 있습니다. 이 기술을 지연 초기화 또는 지연 인스턴스화라고합니다. 지연 초기화의 주요 목적은 성능을 높이고 메모리 공간을 줄이는 것입니다. 유형의 인스턴스를 인스턴스화하는 데 계산 비용이 많이 들고 프로그램에서 실제로 사용하지 않는 경우 CPU주기를 지연 시키거나 낭비하지 않을 수 있습니다.


0

Spring 컨테이너를 사용하고 널 입력 불가능 Bean 필드를 초기화하려는 lateinit경우 더 적합합니다.

    @Autowired
    lateinit var myBean: MyBean

1
다음과 같아@Autowired lateinit var myBean: MyBean
Cnfn

0

변경 불가능한 변수를 사용하는 경우 by lazy { ... }또는 로 초기화하는 것이 좋습니다val . 이 경우 항상 필요할 때 최대 1 회 초기화되도록 할 수 있습니다.

널이 아닌 변수를 원하면 값을 변경할 수 있습니다 lateinit var. 안드로이드 개발에 나중에 같은 이러한 이벤트에 초기화 할 수 있습니다 onCreate, onResume. REST 요청을 호출하고이 변수에 액세스하면 UninitializedPropertyAccessException: lateinit property yourVariable has not been initialized해당 변수가 초기화 할 수있는 것보다 요청이 더 빨리 실행될 수 있기 때문에 예외 가 발생할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.