코딩 (독학)/★ Kotlin

[Kotlin] 늦은 초기화 방법 (lateinit / lazy)

짱득이 2022. 4. 5. 13:27
300x250
반응형


✍ Kotlin에서의 변수 늦은 초기화 방법

늦은 초기화라는 말은 말 그대로 변수의 초기화를 늦게 하는 것이다.

 

예를 들어 변수 a를 사용한다고 치고, a의 첫 값(상태)를 정의하기 어려울 때 어떻게 해야하는가?

대부분의 언어는 아래와 같이 빈 값인 null을 입력 해줄 수 있었다.

var a: Int? =null

 

그러나.. 언젠가 분명 사용 할 변수인데도 불구하고 초기 상태로 null을 사용하는 것은 코틀린의 특성과 맞지 않다는 것이다.

즉, 코틀린의 특성상 null 사용을 지양하는것을 강조하고 있으며, 위험한 상태로 본다.

 

아래 링크에서 참고할 수 있다 싶이 Kotlin은 Java를 포함한 많은 언어에서 발생하는 null 참조 예외 (Null Pointer Exception ; 이하 NPE)를 최대한 제거하여 Null Safety (널 세이프티)를 추구하고자 한다고 한다.

[KotlinLang.org 참조 - Null Safety (널 세이프티) 및 원인과 안전하게 사용하는 방법]
Kotlin은 null 참조의 위험을 제거하는 것을 목표로 합니다.
Java를 포함한 많은 프로그래밍 언어에서 가장 일반적으로 발생하는 오류 중 하나는 Null을 참조하는 멤버에 액세스하면 Null 참조 예외가 발생한다는 것입니다.
Java에서 이것은 Null Pointer Exception, 또는 줄여서 NPE라고도 합니다.

 

이런 상황에서 사용할 수 있는것이 바로 늦은 초기화 lateinit 와 by lazy 선언이다.

언어 공부의 특성상.. 백문이 불여일견, 코드로 살펴보는것이 좋다.

 

lateinit

fun main() : Unit {
	lateinit var textLateInit : String
    
    val mainResult_A : Int = 30
    
    textLateInit = "Main Result : $mainResult_A"
    println(textLateInit)
    
    val mainResult_B : Int = 50
    
    textLateInit = "Main Result : ${mainResult_A + mainResult_B}"
    println(textLateInit)
}
* 출력 값
Main Result : 30
Main Result : 80

위 코드를 설명하자면, lateinit를 사용하여 textLateInit 변수를 선언 해 주었고, 이것은 추후에 어떤 동작의 결과 값을 기반으로 textLateInit를 초기화 해 주는것을 암시한다.

 

그렇게 textLateInit는 총 2번의 값 변동이 생기는데 lateinit 변수 선언부인 2번째 라인을 자세히 보면 var로 선언되어 있다.

위와 같이 늦은 초기화에는 추후에 값이 변동되어야 하므로 값의 변경이 가능한 var로 선언되어야 한다.

 

그렇다면 만약 lateinit를 사용하고 사용하지 않는 (늦게 한번 더 초기화를 하지 않는) 상황이 생긴다면 어떻게 될까?

Exception in thread "main" kotlin.UninitializedPropertyAccessException:
							lateinit property text has not been initialized

위와 같이 컴파일 단계에서 Error가 발생하게 된다. 이것은 잠재적으로 생길 수 있는 Error를 미연에 방지 해 주기도 하기 때문에 귀찮을 수는 있지만 Kotlin만의 강력한 코드 장점이라고 할 수 있다.

 

👀 짚고 넘어가기
lateinit의 경우에는 계속해서 값이 변경될 수 있는 속성이다.
즉, var 형태로 선언 되어야 하며 Primitive Type (Int, Float, Double, Long 등)에는 사용할 수 없으므로 주의하자.

 

by lazy

lazy 의 사전 의미
1. 게으른(=idle)  2. 느긋한, 여유로운

lazy는 위와 같은 의미를 가지고 있다. lateinit와의 가장 큰 차이점이라면 변수 호출 시점이 다르다는 것이다.

 

먼저 코드부터 살펴보자.

fun main() {
	lateinit var text : String
    val textLength : Int by lazy {
    	text.length
    }

	text = "JJANG_Story"
    println(textLength)
}

코드를 해석 해 보면, by lazy 구문을 통해 생성자를 넣어준듯 한 모양이면서도 lateinit라서 초기화 되지 않은 text 변수를 활용 한 모습을 확인 해볼 수 있다.

 

by lazy는 선언 당시에는 초기화를 할 방도가 없지만, 이후에 의존하는 값들이 초기화 된 이후 값을 채워넣고 싶을 때 사용한다.

즉, '호출 시'에 이를 어떻게 초기화 해줄지에 대하여 정의할 수 있는 구문인 것이다.

 

따라서 text가 초기화 된 이후에 textLength를 출력할 때 text.length 속성을 사용하여 textLength라는 변수를 초기화 할 수 있다.

 

선언부를 자세히 보자. val로 선언이 되어있는 것을 볼 수 있다. 이는 단 한번의 늦은 초기화가 이루어지고 이후에는 값이 불변함을 보장하는 구문이다.

 

 

✔ 참고하기 ✔
Android에서는 이전 Activity에서 넘어 온 Intent Bundle Extra 등을 현재 액티비티 멤버 변수에
by lazy로 받아와 선언 해 두고, 사용시에 intent.extra 등으로 번들을 뜯어볼 수 있다.

이렇게 하면 생명주기를 위반하지 않고 안전하게 클래스 전역에서 사용할 수 있는 값을 갖고올 수 있다.

 

lateinit ↔ by lazy의 차이점 비교

기본적으로 둘 다 '늦은 초기화'를 하기 위한 녀석으로, 본질적인 목적은 비슷하다고 할 수 있다.

하지만 가변성과 관련한 특성에서 차이가 있고, 이에 따라 용법을 구분하여 사용한다.

 

* 값 변경이 가능한가?

 - lateinit : 가능 (var 사용) - by lazy : 불가능 (val 사용)

 

* 용법 구분 - lateinit : 초기화 이후, 계속하여 값이 바뀔 수 있을 때. - by lazy : 초기화 이후, 읽기 전용 값으로만 사용할 때. (Read-Only)

 

 

 

* 참고한 링크
https://velog.io/@haero_kim/Kotlin-lateinit-vs-lazy-%EC%A0%95%ED%99%95%ED%9E%88-%EC%95%84%EC%84%B8%EC%9A%94

https://holika.tistory.com/entry/%EB%82%B4-%EB%A7%98%EB%8C%80%EB%A1%9C-%EC%A0%95%EB%A6%AC%ED%95%9C-Kotlin-lateinit%EA%B3%BC-by-lazy%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90

https://thdev.tech/kotlin/2018/03/25/Kotlin-lateinit-lazy/

 

728x90
반응형