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

Sio·2023년 1월 8일
0

늦은 초기화?

안드로이드 기술 면접을 보러갔을 때, 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개의 댓글