코틀린 언어 용어 사전
널 안전lateinit var · by lazy · 지연 초기화

lateinit·lazy

nullable로 만들지 않고 초기화를 미루는 두 방법. lateinit은 var·가변, lazy는 val·최초 접근 시 1회.

null을 허용하지 않으면서 초기화를 미루는 두 가지 방법.

lateinit — 나중에 내가 넣는다

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)   // 여기서 넣는다
        setContentView(binding.root)
    }
}

제약

  • var 에만 (val 불가)
  • 원시 타입 불가 (Int · Boolean 등)
  • nullable 타입 불가
  • 초기화 전에 접근하면 UninitializedPropertyAccessException
if (::binding.isInitialized) { ... }   // 초기화 여부를 확인할 수 있다

lazy — 처음 쓸 때 알아서

private val adapter: UserAdapter by lazy {
    UserAdapter { user -> onUserClick(user) }   // 최초 접근 시 한 번만 실행된다
}

특징

  • val 이다 (한 번 만들어지면 안 바뀐다)
  • 기본이 스레드 안전 (LazyThreadSafetyMode.SYNCHRONIZED)
  • 초기화 로직이 선언부에 붙어 있어 읽기 좋다
// 단일 스레드가 확실하면 동기화 비용을 없앨 수 있다
private val adapter by lazy(LazyThreadSafetyMode.NONE) { UserAdapter() }

무엇을 언제 쓰나

  • lateinit — 초기화 시점과 값을 '내가' 정해야 한다

    • 예: onCreate 에서 주입받는 것 · 프레임워크가 나중에 넣어 주는 것
  • lazy — 값을 만드는 방법이 선언 시점에 이미 정해져 있다

    • 예: 비싼 객체를 처음 쓸 때 만들고 싶다
  • 판단: "만드는 방법을 지금 적을 수 있나?" → 있으면 lazy, 없으면 lateinit

남용하면

lateinit 을 남발하면 nullable 을 숨긴 것과 다르지 않다

  • 초기화 전 접근이 런타임 예외로 터진다
  • 생성자 주입으로 해결할 수 있으면 그게 가장 안전하다

Android 에서 Activity/Fragment 는 생성자를 쓸 수 없어 lateinit 이 관용구가 됐을 뿐, 일반 클래스에서는 다시 생각해 본다

Fragment의 뷰 바인딩 함정

// Fragment 는 뷰가 파괴돼도 인스턴스가 살아남는다
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null        // 반드시 해제 — 안 하면 뷰 계층이 통째로 누수된다
}

여기서 lateinit을 쓰면 메모리 누수가 된다 — 뷰가 파괴돼도 참조가 남기 때문이다.

면접 함정

  • "lazy는 lateinit보다 항상 낫다" → 값을 밖에서 받아야 하면 lazy로는 못 한다.
  • "lateinit은 null 검사가 필요 없다" → 초기화 전 접근은 예외다. 안전해진 게 아니라 시점을 옮긴 것이다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 널 안전 — ?·!!·Elvis·플랫폼 타입·스마트 캐스트