본문으로 바로가기

[Kotlin] 코틀린 constructor vs init block

category 개발이야기/Kotlin 2018. 5. 29. 17:01
반응형


Constructor 와 init

코틀린에서는 class를 초기화 할때 두가지 방법을 제공합니다.
constructor (생성자)를 이용하거나 init()을 사용하여 객체가 생성될때 필요한 초기화 작업을 할 수 있습니다.

그럼 어떤 순서로 두개가 초기화 될까요?

초기화 순서를 명시적으로 알고 있지 않으면 val로 정의한 함수에서 NPE가 발생하는 상황을 만날수도 있습니다.
이런경우 IDE에서 아무런 warning이 발생하지 않을 수 있기 때문에 문제를 파악하기엔 더 어렵습니다.


constructor, initializer

코틀린에서 객체를 생성하면 초기화를 위한 코드들이 호출 되며, 아래와 같은 부분이 초기화 대상 입니다.

Property initalizers

val count: Int = 0
프로퍼트 선언과 동시에 초기화를 진행할 수 있습니다.
위 예제에서는 상수값을 넣었지만 function을 call할수도 있고, by를 이용하여 delegation 시킬 수 도 있습니다.

Initialize blocks

init{
    //do something
}

class에 init {..} 블럭을 넣으면 객체 생성시 호출되어 실행됩니다.

init{..} 블럭은 보통 class 내 상단부분에 넣지만, 중간에 넣어도 되며, 여러개를 넣어도 전부 생성시 호출됩니다.


Constructor

class Person(val name: String ) { //primary constructor
    constructor (age: Int, address: String) { //secondary constructor
        //do something
    }
    
    constructor (age: Int, address: String, company: String) { //secondary constructor
        //do something
    }
}

생성자 역시 class의 객체 생성시 호출됩니다.

class정의와 함께 붙는 primary constructor가 있으며, parameter에 따라 달라지는 secondary constructor를 여러개 만들수도 있습니다.
property와 마찬가지로 내부에서 function을 call할 수 있습니다.


Execution order

그럼 이들이 전부 쓰였을때 생성 순서는 어떨까요?
또한 상속관계에 있다면 어떻게 될까요?

1. 호출한 생성자의 arguments에 대해 수행
2. 만약 해당 생성자에서 다른 생성자를 호출하고 있다면, 호출된 생성자의 arguments가 초기화됨.
3. 상단에서부터 차례대로 property와 init 블록들이 초기화
4. 생성자 블럭 내부의 코드들이 수행됩니다.

특이한 점은 호출된 constructor 블럭 내부의 코드가 제일 마지막에 초기화 된다는점 입니다.

상속관계에 있다면 좀더 복잡해 집니다.

open class Parent {
    private val a = println("Parent.a - #4")

    constructor(arg: Unit=println("Parent primary constructor default argument - #3")) {
        println("Parent primary constructor - #7")
    }

    init {
        println("Parent.init - #5")
    }

    private val b = println("Parent.b - #6")
}

class Child : Parent {
    val a = println("Child.a  - #8")

    init {
        println("Child.init 1 - #9")
    }

    constructor(arg: Unit=println("Child primary constructor default argument - #2")) : super() {
        println("Child primary constructor - #12")
    }

    val b = println("Child.b - #10")

    constructor(arg: Int, arg2:Unit= println("Child secondary constructor default argument - #1")): this() {
        println("Child secondary constructor - #13")
    }

    init {
        println("Child.init 2 - #11")
    }
}


위 코드를 Child(1)을 넣어서 수행하면 가장먼저 secondary constructor의 arguments부터 초기화가 진행됩니다.

결과는 아래와 같습니다.

Child secondary constructor default argument - #1

Child primary constructor default argument - #2

Parent primary constructor default argument - #3

Parent.a - #4

Parent.init - #5

Parent.b - #6

Parent primary constructor - #7

Child.a - #8

Child.init 1 - #9

Child.b - #10

Child.init 2 - #11

Child primary constructor - #12

Child secondary constructor - #13


상속관계 있다면, 부모 클래스의 모든 property, init block, constructor가 생성된 후에 자식 클래스가 초기화 진행됩니다.

따라서 부모 클래스에서 override된 자식 클래스의 함수를 호출하고, 이 함수 내부에 자식클래스의 property에 접근하고 있다면 NPE가 발생하겠죠?


원문: https://medium.com/keepsafe-engineering/an-in-depth-look-at-kotlins-initializers-a0420fcbf546

반응형