Kotlin
[Kotlin] lateinit vs lazy 정리
태오님
2025. 5. 12. 23:02
Kotlin에서는 변수를 나중에 초기화하는 방법으로 lateinit과 lazy를 사용할 수 있다. 둘 다 선언 시 즉시 초기화하지 않고 나중에 값을 설정하거나 계산할 때 사용하지만, 용도와 동작 방식이 다르다.
1. lateinit
개념
- 초기화 지연 변수(Delayed Initialization)
- 주로 var(가변 변수)에 사용
- null을 사용하지 않고 늦게 초기화할 수 있도록 해준다.
특징
- nullable을 피할 수 있음 (var str: String? = null 대신 lateinit var str: String)
- 초기화를 보장하지 않으면 런타임 에러 발생 (UninitializedPropertyAccessException)
- 기본 타입(Int, Boolean 등)에는 사용 불가
→ non-null 객체 타입에서만 사용 가능
lateinit var name: String
fun initName() {
name = "Kotlin"
}
fun printName() {
println(name)
}
2. lazy
개념
- 지연 초기화(Lazy Initialization)
- 주로 val(불변 변수)에 사용
- 변수를 처음 사용하는 순간 초기화 코드가 실행된다.
특징
- 스레드 세이프(thread-safe) (기본값으로 SynchronizedLazyImpl 사용)
- 불변(immutable) 변수에 사용
- 초기화 시점 제어 가능 (LazyThreadSafetyMode 로 동작 방식을 설정할 수 있음)
val name: String by lazy {
println("초기화 중")
"Kotlin"
}
fun printName() {
println(name)
}
3. 자주 헷갈리는 포인트
처음에는 lateinit과 lazy가 비슷해 보인다. 둘 다 "지금은 값을 모르지만 나중에 채우겠다"는 의도로 쓰기 때문이다. 하지만 조금만 쓰다 보면 딱 두 가지에서 갈린다.
- var냐 val이냐
- lateinit은 var만 가능하다.
- lazy는 val만 가능하다.
- 초기화를 내가 직접 할 거냐, 시스템이 알아서 할 거냐
- lateinit은 반드시 내가 명시적으로 초기화 해야 한다.
- lazy는 변수를 처음 읽는 순간 자동으로 초기화된다.
또한, lateinit은 객체 타입에서만 쓸 수 있고, 기본 타입(Int, Boolean 등)에는 적용할 수 없다. 반면 lazy는 이런 제한 없이 쓸 수 있다.
lateinit을 쓰면 초기화 여부를 확인할 수 있는 ::변수명.isInitialized 같은 기능도 제공된다. 반면, lazy는 그런 기능은 없지만, 어차피 처음 접근할 때 자동으로 초기화되기 때문에 신경 쓸 일이 없다.
한마디로 말하면:
- lateinit은 "이건 무조건 내가 채울 거야"
- lazy는 "그냥 필요할 때 알아서 만들어줘"
4. 언제 무엇을 써야 할까?
프로젝트를 하다 보면 이런 고민이 생긴다.
- 뷰 바인딩 객체처럼 나중에 무조건 초기화해야 하는데, null은 쓰기 싫다 → lateinit
- 데이터베이스 연결처럼 쓰일지 안 쓰일지 모르는데, 쓸 때만 초기화하고 싶다 → lazy
단순히 문법적 차이로 접근하면 실무에서는 계속 헷갈리게 된다. 하지만 "책임" 관점으로 보면 명확해진다.
- lateinit은 초기화 책임이 "나한테" 있다.
→ 안 채우면 터진다. - lazy는 초기화 책임을 "시스템한테" 미룬다.
→ 나는 그냥 쓰기만 하면 된다.
초기화 시점, 책임, var/val 여부. 이 세 가지를 기억하면 선택은 간단하다.
5. 정리
- lateinit : "나중에 직접 초기화할게, null 없이"
- lazy : "필요할 때 알아서 초기화해 줘, 불변으로"
용도는 비슷해 보여도 mutable vs immutable, 초기화 시점에 따라 선택이 달라진다. 상황에 맞는 초기화 전략을 선택하는 것이 핵심이다.