Programming/Kotlin

코틀린(Kotlin) - 생성자와 프로퍼티를 갖는 클래스 선언

JunsuKim 2021. 10. 3.
728x90

클래스 생성자

클래스 생성자는 객체를 생성할 때 초기화 블록 다음으로 실행이 된다. 

생성자는 주 생성자(Prime Constructor)와 부 생성자(Secondary Constructor) 이 두 가지로 나뉘게 된다.

주 생성자(Prime Constructor)

주 생성자는 클래스 이름 뒤에 constructor 키워드를 사용하여 지정한다.

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

생성자에 private, internal와 같은 가시성 수식어가 없다면 constructor 키워드는 생략이 가능하다.

init 블록이나 프로퍼티 초기화 식에서만 주 생성자의 파라미터 참조가 가능하다.

기반 클래스 생성자 호출

클래스에 기반 클래스가 있다면 주 생성자에서 기반 클래스의 생성자 호출을 해야 한다.

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

class TistoryUser(nickname: String) : User(nickname)

TistoryUser 클래스가 User 클래스를 상속받고 있다. 그리고 User 클래스의 주 생성자엔 nickname 생성자 프로퍼티가 있다. 따라서 하위 클래스인 TistoryUser에서 기반 클래스인 User를 상속받을 때 생성자를 호출해야 한다.

부 생성자(Secondary Constructor)

부 생성자는 여러 가지 방법으로 인스턴스를 초기화할 방법이 필요할 때 사용한다.

주 생성자가 클래스 이름 뒤에 consturctor 키워드를 사용한다면, 부 생성자는 클래스 몸체에서 constructor 키워드를 통해 정의된다.

open class View {
    constructor(ctx: Context) { . . . . }
    constructor(ctx: Conxtext, attr: AttributeSet) { . . . . }
}

Class Button: View {
    constructor(ctx: Context): this(ctx, MY_STYLE) { . . . . } // 부 생성자 호출
    constructor(ctx:Context, attr: AttributeSet): super(ctx, attr) { } // 상위 클래스 생성자 호출
}

 

주 생성자가 존재하면 부 생성자는 직접 또는 간접적으로 주 생성자를 호출해야 한다.

여기서 간접적으로 호출이 뜻하는 것은 다른 부 생성자를 통해 호출하는 것이다.

open class View(ctx:Context, attr: Attribute, type: Int) {
    constructor(ctx: Context): this(ctx, AttributeSet(), 0)
    constructor(ctx: Context, attr: AttributeSet): this(ctx, attr, 0)
}

this 키워드를 통해 보조 생성자 호출을 한 후 주 생성자를 호출하는 작업이 필요하다. 

주 생성자가 없다면 부 생성자는 반드시 상위 클래스를 초기화하거나 다른 생성자에 생성을 위임해야 한다.

인터페이스에 선언된 프로퍼티 구현

코틀린에서는 인터페이스에 추상 프로퍼티를 추가할 수 있다.

interface User {
    val nickname: String
}

이는 User 인터페이스를 구현하는 클래스(자식 클래스)에서 name의 값에 접근하기 위한 방법을 제공해야 한다.

이러한 방법에는 다음과 같은 것들이 있다.

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

차례로 보면

PrivateUser 클래스에선 추상 프로퍼티를 구현하는 것이므로 override를 한 후 직접 선언을 한다.

SubscribingUser 클래스에서도 마찬가지로 override를 한 후 custom getter를 통해 프로퍼티를 설정한다.

FacebookUser 클래스에선 초기화 식을 이용하여 nickname값을 초기화한다.

 

인터페이스에는 추상 프로퍼티뿐만 아니라 getter와 setter가 있는 프로퍼티도 선언할 수 있다.

interface User {
    val email: String
    val name: String
        get() = email.substringBefore("@")
}

User 인터페이스에 추상 프로퍼티인 email과 custom getter인 name 프로퍼티가 함께 있는 형태이다. 

둘의 차이점으로는 하위 클래스에서 추상 프로퍼티는 반드시 override를 해야 하지만, custom getter를 사용하면 override를 할 필요가 없다.

getter와 setter에서 지원 필드 접근(Backing Field)

class User(val name: String) {
    val address: String = "unspecified"
        set(value: String) {
            println("changed $field -> $value")
            field = value
        }
}

접근자 몸체에서 field라는 식별자로 지원 필드 접근을 한다.

getter는 field 값을 읽을 수만 있고, setter에선 값을 읽거나 쓸 수 있다.

접근자 가시성 변경

접근자의 가시성은 기본적으로 프로퍼티의 가시성과 같다.

또는 get이나 set 앞에 가시성 수식어를 추가하여 가시성을 변경 가능하다.

class LengthCounter {
    var counter: Int = 0
        private set
        
    fun addWord(word: String) {
        counter += word.length
    }
}
728x90

댓글