반응형

코틀린에서 클래스는 class 라는 키워드를 사용해 선언할 수 있습니다.

class SampleClass { /*...*/ }

 

그리고 클래스는 class name, class header, class body로 이루어져 있는데 여기서 클래스 header와 body는 생략 가능합니다. 즉 다음과 같은 형태로도 클래스를 선언할 수 있습니다.

class EmptyClass

 

Primary Constructor

코틀린의 클래스는 하나의 primary constructor다수의 secondary constructor를 가질 수 있습니다.

그리고 primary constructor의 경우 클래스 헤더(클래스 이름 오른편)에 위치합니다.

class Person constructor(name: String) { /*...*/ }

 

이때 만약 primary constructor가 어노테이션이나 접근 제한자(public, private 등)을 갖고 있지 않다면 constructor 키워드를 생략할 수 있습니다.

class Person(name: String) { /*...*/ }

 

primary constructor에서는 어떤 실행문도 포함될 수 없습니다. 만약 초기화하는 코드를 넣고 싶을 경우 init 블럭을 이용하면 가능합니다.

이와 관련해서 코드를 한 번 작성해보겠습니다.

 

class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println)

    init {
        println("First initializer block that prints ${name}")
    }

    val secondProperty = "Second property: ${name.length}".also(::println)

    init {
        println("Second initializer block that prints ${name.length}")
    }
}

fun main(args: Array<String>) {
    val person = InitOrderDemo("Ready Kim")
}
First property: Ready Kim
First initializer block that prints Ready Kim
Second property: 9
Second initializer block that prints 9

 

인스턴스 초기화를 할 때, init 블록과 property 선언 및 초기화는 같은 우선순위를 가져 위에서부터 선언된 순서대로 수행됩니다. 

 

인상 깊게 살펴볼 것은 위 코드의 name과 같이 primary constructor에서 정의된 파라미터를 아래 init 블록에서도 사용할 수 있다는 점입니다. 뿐만 아니라 property 초기화에도 사용할 수 있습니다.

 

사실 primary constructor를 통해 아예 property에 대한 선언과 초기화를 동시에 진행할 수도 있습니다.

class Person(val firstName: String, val lastName: String, var age: Int) { /*...*/ }

 

보통의 프로퍼티와 마찬가지로 primary constructor 내에서 선언되는 property 또한 var과 val로 선언하면 됩니다.(프로퍼티에 관한 글은 여기)

 

Secondary Constructors

코틀린의 클래스는 secondary constructor도 가질 수 있습니다. 이때 사용되는 키워드는 constructor이고, primary constructor와 다르게 생략할 수 없습니다.

 

class Person {
    var children: MutableList<Person> = mutableListOf<Person>();
    constructor(parent: Person) {
        parent.children.add(this)
    }
}

 

위 코드를 보시면 primary constructor가 없기 때문에 body에 구현된 constructor가 primary constructor 처럼 보이실 수 있지만 저 생성자는 secondary constructor 입니다.

 

위 코드처럼 클래스가 primary constructor를 갖고 있지 않을 경우에는 상관없지만, 만약 클래스에 primary constructor가 있을 경우 각 secondary constructor는 this() 생성자를 이용해 직간접적으로 primary constructor에 위임해야 합니다.

 

이에 대한 예시는 아래와 같습니다.

 

class Person(val name: String) {
    var age: Int = 26
    constructor(name: String, age: Int) : this(name) {
        this.age = age
    }
}

 

위 코드에서 this(name)을 명시해주지 않는다면 코틀린은 컴파일 에러를 띄우게 됩니다.

 

그렇다면 primary constructor와 secondary constructor와 init 블럭이 모두 있을 때 어떻게 될까요?

우선 우리가 알아야 할 것은 init 블럭이 primary constructor의 일부라는 것입니다.

 

그리고 primary constructor는 secondary constructor의 첫 번째 실행문으로 실행됩니다.

따라서 init 블럭의 코드는 항상 secondary constructor의 body보다 먼저 실행됩니다.

 

설령, 클래스가 primary constructor를 갖고 있지 않을 때에도 delegation이 암시적으로 일어나기 때문에 init 블럭은 항상 secondary constructor의 body 보다 먼저 실행됩니다.

 

class Person(val name: String) {
    var age: Int

    init {
        age = 26
    }

    constructor(name: String, age: Int) : this(name) {
        println(this.age)
        this.age = age
        println(this.age)
    }
}

fun main(args: Array<String>) {
    val person = Person("Ready Kim", 20)
}
26
20

 

만약 추상 클래스가 아닌 클래스가 어떠한 생성자(primary or secondary)도 선언하지 않았더라면, 코틀린은 자동으로 아무런 파라미터를 갖지 않은 primary constructor를 생성해줍니다. 이는 마치 자바에서 기본 생성자를 자동으로 생성해주는 것과 같습니다.

 

기본적으로 constructor의 생성자는 public입니다. 만약 생성자에 public이 아닌 다른 제한자를 두고 싶은 경우 constructor 키워드를 생략하지 않은 채로 앞에 명시해주면 됩니다.

 

class Person private constructor () { /*...*/ }

 

참고로, 자바와 달리 코틀린에서 생성자를 통해 인스턴스를 생성할 때는 new 라는 키워드를 사용하지 않고 바로 클래스이름과 함께 생성자를 호출하시면 됩니다.

 

 

반응형
반응형