본문으로 바로가기
반응형


이 글은 Kotlin In Action을 참고 하였습니다.

더욱 자세한 설명이나 예제는 직접 책을 구매하여 확인 하시기 바랍니다

4.2 Class의 생성자와 property

코틀린에는 주 생성자와(primary constructor)와 부생성자가(secondary constructor)가 존재합니다.

주 생성자는 class 선언과 함께 선언하고, 부 생성자는 추가적인 생성자가 필요할때 사용합니다.


4.2.1 주 생성자와 초기화(init)

class User(val nickname: String)

주 생성자는 클래스 선언과 함께 정의됩니다.


class User constructor(_nickname: String) {
    val nickname: String

    init {
        nickname = _nickname
    }
}

constructor는 주 생성자를 나타내기 위한 키워드이나 한정자나, 다른 키워드가 붙지 않는다면 생략하는것도 가능합니다.

또한 init{..}은 초기화 작업을 할때 사용합니다.

주로 클래스가 객체로 생성될때 초기화 하는 작업이 들어갑니다.


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

자바에서 하는것처럼 이렇게 사용해도 무방합니다.

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

fun main(args: Array) {
    val alice = User("Alice")
    println(alice.isSubscribed)
    val bob = User("Bob", false)
    println(bob.isSubscribed)
    val carol = User("Carol", isSubscribed = false)
    println(carol.isSubscribed)
}

주 생성자에 default값을 지정해서 사용해도 됩니다.

만약 모든 생성자에 default값을 넣는다면, 컴파일러는 기본적으로 아무것도 없는 생성자를 생성합니다.

그리고 나서 기본값을 대입하는 작업을 합니다.


만약 상속을 통해서 class를 만드는경우 부모의 생성자가 있다면 자식 클래스에서 반드시 호출해야 합니다.

open class Person(val age: Int) {...}

class Batman(val age: Int): Person(int) {
...
}
생성자를 정의하지 않으면 기본으로 인자가 없는 생성자를 만들어줍니다.

(자바랑 동일한 동작을 합니다.)



따라서 상속을 받을때는 부모클래스를 명시하고 ()붙여야 합니다.

기본 생성자를 호출하겠단 의미 입니다.

반대로 interface는 생성자가 없으니 ()를 붙일수 없습니다.

()가 있고 없고에 따라서 클래스의 상속인지, 인터페이스의 implements인지를 구분 할 수 있습니다.


생성자를 외부에 노출하지 않으려면 private을 이용하여 생성자를 생성합니다.

class Superman private constructor() {...}

다만 이렇게 하면 클래스 내부에서만 생성자에 접근할수 있는데, 어디서 많이 보던 패턴입니다.

코틀린에서 singleton을 만들때 생성자를 외부에 노출하지 않는 방법이며, 싱글톤을 완벽하게 구현하려면 object을 이용해야하는데, 이는 다음번에 다루겠습니다.


4.2.2 부 생성자(secondary constructor)

생성자가 여러개 필요할때는 부 생성자를 생성하여 사용합니다.

class TextView: View {
    constructor(context: Context) : this(context, null) {
        ....
    }

    constructor(context: Context, attr: AttributeSet) : super(cotext, attr) {
        ....
    }

이때 상속받은 부모의 생성자는 꼭 호출해야 합니다.

부모의 생성자는 super(), 내 생성자중 다른 생성자는 this()로 호출이 가능합니다.

클래스의 주 생성자가 없다면, 모든 부 생성자들은 상위 클래스를 초과 하거나, 다른생성자에게 이를 위임해야 합니다.


4.2.3  인터페이스에 property 추가

인터페이스에 property를 추가할 수 있습니다. (자바에서는 말도 안되는 일이지만...)
단 이 property는 상태를 가질 수 없기 때문에 자식 클래스에서 반드시 값에 접근하기 위한 방법을 제공해야만 합니다.

fun getFacebookName(accountId: Int) = "fb:$accountId"

interface User {
    val nickname: String
}
class PrivateUser(override val nickname: String) : User

class SubscribingUser(val email: String) : User {
    override val nickname: String
        get() = email.substringBefore('@')
}

class FacebookUser(val accountId: Int) : User {
    override val nickname = getFacebookName(accountId)
}

fun main(args: Array) {
    println(PrivateUser("test@kotlinlang.org").nickname)
    println(SubscribingUser("test@kotlinlang.org").nickname)
}

User는 nickname이란 property를 가지고 있습니다. 

  1. PrivateUser는 주 생성자에서 override 메서드를 이용해서 부모의 property값을 처리 합니다.
  2. SubscribingUser의 경우 class 내부에서 명시적으로 nickname을 override하고 custom getter를 제공합니다.
  3. FacebookUser는 함수를 이용하여 nickname값을 설정 합니다.


여기서 2번과 3번은 살짝 뉘앙스가 다릅니다.

SubscribingUser는 getter를 호출할 때 마다 값을 계산해서 반환합니다.

반면, FacebookUser는 객체 생성지 한번만 값을 구해서 backing field에 설정해 놓고 호출때마다 저장된 field을 반환합니다.


4.2.4 Backing Field

propertys는 해당 값을 저장하기 위해 Backing field를 사용합니다.
컴파일러에서 자동 생성해 주는 이 field는 아래 코드처럼 접근할 수 있습니다.

class User(val name: String) {
    var address: String = "unspecified"
        set(value: String) {
            println("""
                Address was changed for $name:
                "$field" -> "$value".""".trimIndent())
            field = value
        }
}

fun main(args: Array) {
    val user = User("Alice")
    user.address = "Elsenheimerstrasse 47, 80687 Muenchen"
}

getter에서는 field값을 읽기만 가능하고, setter에서는 읽고 쓰기가 가능합니다.

4.2.5 get과 set의 접근자(한정자) 제어

class LengthCounter {
    var counter: Int = 0
        private set

    fun addWord(word: String) {
        counter += word.length
    }
}

fun main(args: Array) {
    val lengthCounter = LengthCounter()
    lengthCounter.addWord("Hi!")
    println(lengthCounter.counter)
}


위 코드에서 counter의 set을 private을 한정했습니다.

따라서 counter property는 외부에서 접근할 수가 없습니다.


반응형