late(늦은) init(초기화), 즉 초기화를 늦게하는 변수이다.
코드부터 살펴보자.
fun test() {
val a
// ... 뭔가 로직1
if(로직1 == true) a = "hello"
else a = "world"
// ... 뭔가 로직2
println(a)
}
일단 위의 코드를 실행하면, 아니 실행할 수도 없다.
"This variable must either have a type annotation or be initialized" 가 당신의 발목을 잡을 것이기 때문이다.
이렇게 로직1번을 수행하고 나서 a를 결정해야 될 경우 여간 난처하지 않을 수가 없다.
"val a: String? = null 로 선언해 놓고, 나중에 수정하면 되잖아."
하지만, 얘는 나중에 무조건 사용할 건데, 위험하게 null로 놔두자니 여간 찝찝한게 아니다.
이래서 필요한 것이 lateinit이다.
fun test() {
lateinit var a: String
// ... 뭔가 로직1
if(로직1 == true) a = "hello"
else a = "world"
// ... 뭔가 로직2
println(a)
}
이렇게 초기화를 늦게하는 변수라는 것을 선언해주고, 사용할 경우 문제가 깔끔하게 해결된다.
심지어 lateinit은 var과 함께 사용해야 하기 때문에, 초기화 이후에도 값을 계속 바꿀 수 있다.
"그럼 lateinit해놓고 초기화 안하는 경우도 문제 아니야?"
맞다. 그래서 lateinit으로 선언해놓고 초기화하지 않는 경우, 컴파일 단계에서 에러를 알려주기 때문에, 잠재적인 에러들을 나름 방지할 수 있다는 장점이 있다.
자 그러면, lazy는 또 무엇인가. 코드부터 봐보자
fun test() {
lateinit var a: String
val aLength: Int by lazy {
a.length
}
// ... 뭔가 로직1
if(로직1 == true) a = "hello"
else a = "world"
// ... 뭔가 로직2
println(a)
println(aLength)
}
by lazy 라는 구문을 사용했다. 잘 보면, 아직 초기화를 하지 않은 a의 length를 사용하여 지정을 해준 모양이다.
by lazy 는 선언 당시에는 초기화하기 힘들지만, 사용하고자 하는 값이 초기화 된 이후에 값을 채워넣고 싶을 때 사용한다.
따라서, a가 초기화된 후에 a의 길이만큼 자동으로 a의 길이만큼 자동으로 초기화를 가능하게 해준 부분이 저 by lazy 인 것이다.
정리하자면, 둘 다 나중에 초기화하기 위해 사용하는 친구들이다.