Tiny Star
본문 바로가기
Dev Log/Kotlin

[Kotlin] 클래스 초기화의 비밀: init 블록과 실행 순서 완벽 가이드

by 별터 2026. 1. 4.

코틀린으로 개발을 하다 보면 클래스 내부에 프로퍼티를 선언하고,init 블록에서 초기화 로직을 수행하는 경우가 많습니다. 하지만 코드가 복잡해질수록 "도대체 이 코드는 언제 실행되는 거지?"라는 의문이 들 때가 있습니다.

 

특히 프로퍼티 선언, init 블록, 생성자 로직이 뒤섞여 있을 때 초기화 순서를 잘못 이해하면 예기치 않은 NullPointerException이나 의도치 않은 값 할당 문제를 겪을 수 있습니다.

 

오늘은 코틀린 클래스의 초기화 순서init 블록의 올바른 위치, 그리고 가독성을 높이는 코드 배치 규칙에 대해 깊이 있게 알아보겠습니다.


1. 코틀린 클래스 초기화의 대원칙

 

코틀린 클래스가 인스턴스화될 때 실행되는 순서는 명확한 규칙을 따릅니다. 이 규칙만 기억해도 대부분의 초기화 버그를 잡을 수 있습니다.

Rule: 위에서 아래로 (Top-down)

 

클래스 본문(Body)에 있는 프로퍼티 초기화초기화 블록(init) 은 작성된 순서대로 실행됩니다.


2. 예제 코드로 보는 실행 순서

 

아래 코드를 보며 실행 결과를 예측해 볼까요?

class InitializationOrder(name: String) {
    // 1. 주 생성자의 매개변수는 이미 사용 가능함
    val firstProperty = "1번 프로퍼티: $name".also { println(it) }

    // 2. 첫 번째 init 블록
    init {
        println("2번: 첫 번째 init 블록 실행")
    }

    // 3. 두 번째 프로퍼티
    val secondProperty = "3번 프로퍼티".also { println(it) }

    // 4. 두 번째 init 블록
    init {
        println("4번: 두 번째 init 블록 실행")
    }

    // 5. 보조 생성자 (Constructor)
    constructor() : this("기본값") {
        println("5번: 보조 생성자 실행")
    }
}

fun main() {
    InitializationOrder
}

 

[실행 결과]

1번 프로퍼티: 기본값
2번: 첫 번째 init 블록 실행
3번 프로퍼티
4번: 두 번째 init 블록 실행
5번: 보조 생성자 실행

 

해설:

  1. 객체 생성이 시작되면 가장 먼저 주 생성자(Primary Constructor)가 호출됩니다.
  2. 클래스 내부로 들어와서 위에서부터 순서대로 프로퍼티 할당과 init 블록이 실행됩니다.
  3. 모든 초기화(init 및 프로퍼티)가 끝난 후에야 보조 생성자(constructor)의 본문이 실행됩니다.

3. 흔히 하는 실수: 선언 순서의 중요성

 

init 블록은 주 생성자의 일부로 간주됩니다. 따라서 init 블록 안에서 클래스의 프로퍼티를 사용할 때는 반드시 그 프로퍼티가 init 블록보다 위에 선언되어 있어야 안전합니다.

class BadExample {
    init {
        // 컴파일 에러 혹은 런타임 에러(null 접근) 가능성 있음
        // println(name.length)
    }

    val name = "Kotlin"
}

위 코드는 name이 초기화되기 전에 init 블록이 먼저 실행되려 하므로 문제가 발생합니다. 코틀린 컴파일러는 이를 'Illegal Forward Reference'로 막아주지만, 복잡한 로직에서는 이 순서를 놓치기 쉽습니다.


4. Clean Code: 권장하는 코드 배치 순서

 

우아한테크코스나 실무 프로젝트 등에서 협업을 위해 권장되는 클래스 내부의 배치 순서는 다음과 같습니다. 읽는 사람이 코드의 흐름을 위에서 아래로 자연스럽게 읽을 수 있도록 하는 것이 핵심입니다.

  1. 상수 (const val): 클래스 수준의 상수가 있다면 최상단 혹은 companion object의 최상단
  2. 프로퍼티 (Property): 상태를 나타내는 값들
  3. init 블록: 초기화 및 유효성 검증 로직
  4. 보조 생성자 (Secondary Constructor): 필요한 경우
  5. 메서드 (Method):
    • 공개 함수 (public)
    • 비공개 함수 (private) - public 함수 바로 아래나 파일 하단에 배치
class Lotto(private val numbers: List<Int>) {
    // 1. 프로퍼티 (주 생성자 포함)

    // 2. init 블록 (유효성 검사)
    init {
        require(numbers.size == 6) { "로또 번호는 6개여야 합니다." }
    }

    // 3. 공개 메서드
    fun contains(number: Int): Boolean {
        return numbers.contains(number)
    }

    // 4. 비공개 메서드 (내부 로직)
    private fun validateRange() { ... }
}

5. 마무리

 

init 블록은 단순한 코드 블록이 아니라, 객체가 태어나는 순간 가장 먼저 수행해야 할 의무(유효성 검사, 초기 세팅)를 담당하는 중요한 공간입니다.

 

"프로퍼티 -> Init -> 생성자" 순서를 기억하고, 항상 코드를 위에서 아래로 흐르듯이 작성하는 습관을 들인다면 훨씬 더 예측 가능하고 견고한 코틀린 코드를 작성할 수 있을 것입니다.