Kotlin에서 init 블록은 언제 실행되고 왜 쓰는가

ShaynePark·2025년 11월 8일

Kotlin

목록 보기
2/2

최근 코틀린 공부를 하다가 클래스 안에서 init 블록이라는 걸 자주 보게 되었습니다. 처음엔 “이게 그냥 생성자 같은 건가?” 하고 넘겼는데, 실제로 눈으로 확인하고 동작을 분석하다 보니 생각보다 미묘한 부분이 많더라고요. 특히 프로퍼티 초기화 순서, 상속 관계에서의 실행 순서, 그리고 보조 생성자와 같이 쓸 때의 동작 방식이 헷갈렸어요. 그래서 이번엔 제대로 이해하고 싶어서 정리해봤습니다. 이 글은 코틀린을 어느 정도 써봤지만 init의 정확한 타이밍이나 역할이 궁금했던 분들께도 도움이 될 거라 생각합니다.


init 블록이란?

init 블록은 클래스 인스턴스가 생성될 때 실행되는 초기화 코드입니다.
주 생성자(primary constructor)와 함께 작동하며, 보통 생성자의 매개변수를 이용해 프로퍼티를 세팅하거나 검증할 때 자주 사용됩니다.

init 블록은

“객체가 만들어질 때 자동으로 실행되는 설정 코드 공간” 이에요.

그리고 중요한 점은 init 블록은 여러 개 쓸 수 있고, 클래스 본문에 작성한 순서대로 실행됩니다.

실행 순서 예시

class Person(val name: String, val age: Int) {
    val displayName = "이름: $name".also { println("프로퍼티 displayName 초기화: $it") }

    init {
        println("첫 번째 init: name=$name, age=$age")
    }

    val greeting = run {
        println("프로퍼티 greeting 초기화")
        "안녕하세요, $name 입니다."
    }

    init {
        println("두 번째 init: greeting='$greeting'")
    }
}

fun main() {
    val p = Person("로그", 29)
}

출력순서
1. 프로퍼티 displayName 초기화: 이름: 로그
2. 첫 번째 init: name=로그, age=29
3. 프로퍼티 greeting 초기화
4. 두 번째 init: greeting='안녕하세요, 로그 입니다.'

코틀린은 클래스 안에 있는 프로퍼티와 init 블록을 위에서 아래로 순서대로 실행합니다.
즉, init은 모든 프로퍼티 초기화가 끝난 뒤 한 번에 실행되는 블록이 아니라, 선언된 위치에 따라 중간중간 실행되는 구조예요.

프로퍼티 초기화식과 init 블록 언제 어떤것을 쓸까?

상황추천 방식
단순 계산, 표현식프로퍼티 초기화식 (val x = ...)
여러 프로퍼티를 한꺼번에 설정해야 할 때init 블록
검증, 로깅, 예외 처리 등 부가 로직 필요init 블록
주 생성자 파라미터로부터 여러 프로퍼티를 파생시킬 때init 블록

간단한 건 프로퍼티 초기화식으로,
복잡하거나 여러 변수를 다뤄야 한다면 init 블록으로 옮기면 됩니다.

여러 init 블록이 있을 때

여러 개를 써도 됩니다. 다만 작성 순서 그대로 실행돼요

class Example {
    val a = alsoPrint("a 초기화")
    init { alsoPrint("첫 번째 init") }
    val b = alsoPrint("b 초기화")
    init { alsoPrint("두 번째 init") }

    private fun alsoPrint(msg: String): String {
        println(msg)
        return msg
    }
}

위의 실행결과는 다음과 같습니다.

a 초기화
첫 번째 init
b 초기화
두 번째 init

상속에서의 실행 순서

여기가 조금 헷갈렸던 부분입니다. 정리하자면 다음 순서로 초기화가 이뤄져요.

  1. 부모 클래스의 프로퍼티 초기화식
  2. 부모 클래스의 init 블록
  3. 자식 클래스의 프로퍼티 초기화식
  4. 자식 클래스의 init 블록
open class Parent(val p: String) {
    val parentProp = also("parentProp 초기화")
    init { println("Parent init: p=$p") }

    fun also(msg: String): String {
        println(msg)
        return msg
    }
}

class Child(val c: String): Parent("부모 파라미터") {
    val childProp = also("childProp 초기화")
    init { println("Child init: c=$c") }
}

fun main() {
    Child("자식 파라미터")
}

출력순서는 다음과 같습니다.

parentProp 초기화
Parent init: p=부모 파라미터
childProp 초기화
Child init: c=자식 파라미터

부모가 먼저, 자식이 나중.
자식의 프로퍼티는 부모 초기화가 끝난 뒤에야 실행됩니다.

보조 생성자

보조 생성자를 추가하면, init은 항상 주 생성자 호출 이후에 실행됩니다.

class User(val id: Int, val name: String) {
    init { println("init: id=$id, name=$name") }

    constructor(id: Int): this(id, "unknown") {
        println("보조 생성자 실행")
    }
}

fun main() {
    User(1)
}

실행 결과는 다음과 같습니다.

init: id=1, name=unknown
보조 생성자 실행

init에서 예외 던지기 (검증로직)

init은 인스턴스가 만들어질 때 무조건 실행되기 때문에 검증(validation) 로직을 넣기에 아주 좋은 위치에요. 저도 실전에서는 이 방식을 가장 많이 보고 접하고 있어요.

class Account(val balance: Int) {
    init {
        require(balance >= 0) { "잔액은 0 이상이어야 합니다." }
    }
}

이렇게 하면 Account(-1) 생성 시 바로 예외를 던져
잘못된 객체가 만들어지는 걸 막을 수 있습니다.

실무에서 자주 쓰는 init 패턴

실제로는 init을 이렇게 활용하는 경우가 많습니다

상황활용 예시
입력값 검증require, check로 유효성 검사
데이터 변환 초기화주 생성자 파라미터 가공 후 프로퍼티 세팅
로그 기록객체 생성 시점 로깅 (단, 사이드 이펙트 주의)
간단한 DI 초기화DI 컨테이너 없이 의존성 주입 전에 셋업
class User(name: String) {
    val name: String

    init {
        require(name.isNotBlank()) { "이름은 비어 있을 수 없습니다." }
        this.name = name.trim()
        println("User 객체 초기화 완료: $name")
    }
}

initcompanion object 의 초기화 차이

class Sample {
    companion object {
        init {
            println("컴패니언 초기화 - 클래스 로드 시 1회 실행")
        }
    }

    init {
        println("인스턴스 초기화 - 객체 생성 시마다 실행")
    }
}

companion object는 클래스 로드 시 딱 한 번만 실행,
init은 인스턴스가 만들어질 때마다 실행된다는 차이가 있습니다.


마무리하며

처음엔 init이 “그냥 생성자 같은 거겠지” 싶었는데, 계속 보다 보니 ‘클래스 내부의 실행 순서’와 ‘객체의 생명주기’를 명확히 보여주는 블록이더라고요. 특히 프로퍼티 초기화, 상속, 보조 생성자와의 관계를 직접 실행해보면 “언제 어떤 코드가 실행되는지” 감이 훨씬 잘 옵니다. 저도 이번 글을 정리하면서 “아, 이래서 init이 꼭 필요한 구문이었구나” 하고 다시 한 번 느꼈어요. 혹시 이 글을 보고 직접 따라 돌려본 분이 있다면, 분명 예전보다 클래스 초기화 구조가 눈에 더 잘 들어올 거예요. 긴 글 읽어주셔서 감사합니다.

0개의 댓글