클래스와 오브젝트#3 - 클래스 생성자

2018, Jun 17    

코틀린에서도 자바와 같이 생성자를 하나 이상 선언할 수 있다.
다만 다른 점이 있는데, 코틀린에서 생성자는 주 생성자부 생성자로 구분된다.

먼저 주 생성자Primary Constructor를 살펴보자.
주 생성자는 클래스 선언문의 header영역의 한 부분으로 class 이름 다음에 위치한다.

class User constructor(val nickname: String) {}

또한 별다른 어노테이션이나 가시성 변경자(visibility modifier)가 없는 경우 constructor 키워드는 생략 가능하며, 별다른 초기화할 코드가 없다면 class body부분인 중괄호도 생략 가능하다.

//constructor keyword생략 가능
class User (nickname: String) {}

//class body부분 생략 가능
class User (nickname: String)

주 생성자는 다음과 같은 역할을 한다.

  • 생성자 파라미터를 선언
  • 그 생성자 파라미터에 의해 초기화 되는 프로퍼티를 정의

주 생성자는 파라미터(or프로퍼티)선언 외에 초기화에 필요한 코드를 추가 할 수 없기 때문에,
초기화 블록(initializer blocks)이라는 것을 사용할 수 있다.

초기화 블록은 init키워드로 블록으로 클래스 객체의 instance가 생성될때 주 생성자와 함께 사용되며
필요하다면 class body안에 여러개 선언할 수 있다.

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}")
    }
}

>>>InitOrderDemo("hello")
First property: hello
First initializer block that prints hello
Second property: 5
Second initializer block that prints 5

초기화 블록없이 class body안에서 주생성자로 전달된 파라미터를 이용하여 프로퍼티를 초기화 할수 있다.

class User(_nickname:String) {
  val nickname = _nickname
}

주 생성자 안에 val을 추가하는 방식으로 프로퍼티 정의와 초기화를 한번에 할 수 있다.

class User(val nickname:String)

또한 함수 파라미터와 마찬가지로 생성자 파라미터에도 디폴트 값을 정의 할 수 있다.

class User(val nickname:String, val isSubscribed: Boolean = true)

클래스의 인스턴스를 만들려면 new 키워드 없이 생성자를 직접 호출하면 된다.

//첫번째 파라미터만 전달
>>> val hyun = User("현석")
>>> println(hyun.isSubscribed)
true

//모든 인자를 순서대로 전달
>>> val gye = User("계명", false)
>>> println(gye.isSubscribed)
false

//생성자 인자 중 일부에 대해 이름을 지정할 수도 있다.
>>> val hye = User("혜원", isSubscribed = false)
>>> println(hye.isSubscribed)
false

//정의된 생성자 파라미터명과 다른 이름을 지정한 경우 오류 발생
>>>val hye = User("혜원", isSubsciption = false)
error: cannot find a parameter with this name: isSubscribe
val hye = User("혜원", isSubscribe = false)
  • 클래스를 정의할 때 별도로 생성자를 정의하지 않으면 컴파일러가 자동으로 아무 일도 하지 않는 인자가 없는 디폴트 생성자를 만들어준다.
    //인자가 없는 디폴트 생성자
    open class Button
    

    아무 인자도 받지 않지만, Button 클래스를 상송한 하위 클래스는 반드시 Button클래스의 생성자를 호출해야 한다.

    //인자가 없는 클래스를 상속받을 때
    class RadioButton : Button()
    

    또한 상위 클래스의 생성자에 파라미터가 선언되어 있다면 인자도 넘겨야 한다.

//상위 클래스의 생성자에 파라미터를 넘기지 않아서 오류 발생
>>> open class Button(w:Int, h:Int)
>>> class RadionButton:Button()

error: no value passed for parameter 'w'
class RadionButton:Button()
                          ^
error: no value passed for parameter 'h'
class RadionButton:Button()

부 생성자Secondary Constructor

일반적으로 코틀린에서는 생성자가 여럿 있는 경우가 자바보다 훨씬 적다. 새로운 생성자가 필요한 상황 중 상당수는 코틀린의 디폴트 파라미터 값과 이름붙은 인자 문법을 사용해 해결할 수 있다.

  • Tip. 인자에 대한 디폴트 값을 제공하기 위해 부 생성자를 여럿 만들지 말고 파라미터의 디폴트 값을 생성자 시그니처에 직접 명시하자.

하지만 인자의 갯수가 다른 부 생성자가 필요한 경우 다음과 같이 정의할 수 있다.

//주생성자가 없는 클래스의 부 생성자
open class View {
  constructor(ctx: String) { //... }

  constructor(ctx: String, attr: String) { //... }
}


아래는 주 생성자가 없는 View객체를 상속하는 부 객체에서 상위객체를 초기화 하는 방식의 여러 예이다.

//class MyView0: View() 인자가 없는 생성자가 존재 하지 않으므로 오류 발생

class MyView1(ctx:String): View(ctx)

class MyView2: View(ctx="merong")

class MyView3(ctx:String, attr:String): View(ctx, attr)

class MyView4(): View(ctx="1", attr="2")

class MyView5: View(ctx="1", attr="2")

class MyView6(ctx:String, attr:String): View(ctx)
  • 클래스에 주 생성자가 없다면 모든 부 생성자는 반드시 상위 클래스를 초기화하거나 다른 생성자에게 생성을 위임해야 한다. 이 객체를 상속할때 하위 객체의 부 생성자에서는 super() 키워드를 통해 자신에 대응하는 상위 클래스 생성자를 호출할 수 있다.
class MyView: View {
    constructor(ctx:String): super(ctx) {
        //...
    }

    constructor(ctx: String, attr: String): super(ctx, attr) {
        //...
    }
}

또한 this() 키워드를 통해 자신의 다른 부생성자를 호출하여 생성자 처리를 위임(delegate)할 수 있다.

class MineView: View {
    constructor(ctx: String):this(ctx, "strong") {
        //부 생성자
    }

    constructor(ctx: String, attr: String): super(ctx) {
        //부 생성자
    }
}
//주생성자가 있는 클래스의 부 생성자
open class Dog(val name:String) {

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

//주 생성자가 있는 클래스를 상속
class MyDog1(name: String): Dog(name)

class MyDog2: Dog(name="myDog")

class MyDog3(name: String, age:Int): Dog(name)