[Kotlin] 코틀린의 늦은 초기화, lateinit & lazy

Sio·2023년 1월 8일

늦은 초기화?

안드로이드 기술 면접을 보러갔을 때, lateinit과 lazy의 차이를 말해달라는 질문을 받았었습니다.

당시 제대로 개념을 알고 있지 않았기에 저는 막연하게 알고있던 점을 어버버,,

lateinit는 늦은 초기화 이후에 계속 값을 변화시킬 수 있고,

lazy는 값 선언시에 초기화 구문을 작성해서 호출시에 단 한번 초기화 하게 되는데
만약 제대로 된 값을 정의하지 않고 호출하게되면 NPE가 발생할 수도 있다는 차이점이 있습니다 (>>😱😱😱)

면접관 님 : NPE가 발생한다구요? lazy에서요?
나 : 아 ㅎㅎㅎ^^ (큰일났다)

그리고 집에와서 다시 복기를 해보며 왜 저런 반응을 하셨는지 너무 이해가 되었기 때문에,, 코틀린의 늦은 초기화를 정리해보려합니다!


코틀린으로 프로그래밍을 하다보면 객체를 늦게 초기화 해야할 때가 있습니다.
즉, 지금 당장 intialize를 할 필요가 없다는 말입니다.

안드로이드 코드를 작성할 때를 생각해보면,,
categoryList를 띄우는 recyclerView의 adapter를 연결하는 상황을 생각해봅시다.

private lateinit var categoryAdapter: CategoryAdapter

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
	super.onViewCreated(view, savedInstanceState)
    initAdapter()
}

// 어댑터 초기화
private fun initAdapter() {
	val categoryList = listOf<Category>(
    	Category.BUSINESS,
        Category.ENTERTAINMENT,
        Category.GENERAL,
        Category.HEALTH,
        Category.SCIENCE,
        Category.SPORTS,
        Category.TECHNOLOGY
    )

   categoryAdapter = CategoryAdapter(categoryList = categoryList, onClick = { navigateToDetailCategory(it) })
   binding.categoryRv.adapter = categoryAdapter
}

위 코드로 카테고리를 띄우는 Grid 리스트 화면을 만들 수 있습니다.

어쨌든 중요한 것은 categoryAdapter를 lateinit 키워드를 이용하여 선언하였다는 것입니다.

categoryAdapter는 분명 사용될 것이지만, 첫 상태를 정의하기 어려울 때가 있을 것입니다.
지금은 이미 정해진 categoryList 값을 사용하지만 이 값이 서버에서 내려주는 값이라면? 계산 후에 정해지는 값이라면? 등의 상황 말이죠..

그러면 아래와 같이 null로 일단 초기화 하자! 할 수도 있지만 코틀린은 자바에 비해 Null Safety를 적극적으로 지원해주는 언어입니다. 굳이 위험하게 null로 설정해야할까요?

var categoryList? = null

그래서 늦은 초기화를 위한 lateinitlazy가 존재합니다✨


lateinit

fun main() {
	lateinit var result: String
    
    val result1 = 10
   	result = "Result : ${result1}"
    println(result)
    
    val result2 = 20
    
    result = "Result : ${result1 + result2}"
    println(result)
}

// 실행 결과
Result : 10
Result : 30

lateinitvar로 선언하기 때문에, 늦은 초기화 이후에도 값이 계속해서 바뀔 수 있습니다. 처음 늦은 초기화 10으로 해준 뒤 30으로 결과를 바꿀 수 있는것이 보이시나요?

만약 lateinit을 사용했는데도 초기화를 안해준다면,,

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

와 같은 컴파일 오류를 발견할 수 있습니다.

잠깐! 📌
lateinit의 경우에는 값이 계속 변경될 수 있으므로 무조건 var를 사용해야하며,
Primitive Type(Int, Float, Double, Long 등) 에는 사용할 수 없습니다.


lazy

다음은 lazy 입니다.

fun main() {
	lateinit var result1: Int
    
    val result: String by lazy {
    	"Result : $result1"
    }
    
    result1 = 10
    println(result)
}

// 실행 결과
Result : 10

lazy를 사용하는 경우 var를 사용할 수 없고 val를 사용하는 경우만 가능합니다.
by lazy 블록 내에서 초기화에 필요한 코드를 작성합니다. (✨ 블록내의 맨 마지막 줄의 결과를 변수에 할당하게 됩니다!)

lateinit var와 다르게 선언과 동시에 호출 시에 어떻게 초기화 해줄지에 대해 정의 해야한다는 점에서 차이가 있습니다.

변수 선언과 동시에 초기화를 선언하지만, result라는 값 호출 시점에서 단 한번의 초기화가 이루어집니다.

val로 선언되었기 때문에 늦은 초기화 이후에는 값이 불변함을 보장합니다.


정리

그래서 늦은 초기화를 왜 사용하냐?
✅ 지금 당장 값을 선언하기 어렵거나, 바로 사용하지 않을 변수인데 -> null로 선언하지 않으려할때 사용하게 됩니다. (Null Safety 보장, NPE 방지)

✅ 메모리 누수를 방지하고, 실행시간을 향상시켜 성능을 개선할 수 있습니다.

lateinit과 lazy는 목적은 비슷하나, 가변성에 관한 특성이 다릅니다.

  • 값이 변경 가능한가?
    - lateinit : 가능 (var), 초기화 이후에 계속해서 값이 바뀔 수 있을 때
    • lazy : 불가능 (val), 초기화 이후에 read-only 값으로 사용할 때
profile
나는 시오

0개의 댓글