코틀린은 주 생성자와 부 생성자를 구분한다. 또한 코틀린에서는 초기화 블록을 통해 초기화 로직을 추가할 수 있다.
4.2.1 클래스 초기화: 주 생성자와 초기화 블록
아래의 세 가지 User 선언은 모두 같다.
class User constructor(_nickname: String) { // 파라미터가 하나만 있는 주 생성자
val nickname: String
init { // 초기화 블록
nickname = _nickname
}
}
class User(_nickname: String) {
val nickname = _nickname
}
class User(nickname: String)
하지만 마지막 선언이 가장 간결하다.
함수 파라미터와 마찬가지로 생성자 파라미터에도 디폴트 값을 정의할 수 있다.
class User(val nickname: String, val isSubscribed: Boolean = true)
클래스에 기반 클래스가 있다면 주 생성자에서 기반 클래스의 생성자를 호출해야 할 필요가 있다. 기반 클래스를 초기화하려면 기반 클래스 이름 뒤에 괄호를 치고 생성자 인자를 넘긴다.
open class User(val nickname: String) { ... }
class TwitterUser(nickname: String) : User(nickname) { ... }
클래스를 정의할 때 별도로 생성자를 정의하지 않으면 컴파일러가 자동으로 아무 일도 하지 않는 인자가 없는 디폴트 생성자를 만들어준다.
open class Button // 인자가 없는 디폴트 생성자가 만들어진다
위 Button 클래스의 생성자는 아무 인자도 받지 않지만, Button 클래스를 상속한 하위 클래스는 반드시 Button 클래스의 생성자를 호출해야 한다.
class RadioButton : Button()
어떤 클래스를 클래스 외부에서 인스턴스화하지 못하게 막고 싶다면 모든 생성자를 private으로 만들면 된다.
class Secretive private constructor() {} // 이 클래스의 주 생성자는 비공개다
4.2.2 부 생성자: 상위클래스를 다른 방식으로 초기화
open class View {
constructor(ctx: Context) { ... } // 부 생성자
constructor(ctx: Context, attr: AttributeSet) { ... } // 부 생성자
}
이 클래스를 확장하면서 똑같이 부 생성자를 정의할 수 있다.
class MyButton : View {
// 상위 클래스의 생성자를 호출한다.
constructor(ctx: Context)
: super(ctx) { ... }
constructor(ctx: Context, attr: AttributeSet)
: super(ctx, attr) { ... }
}
생성자에 this()를 통해 클래스 자신의 다른 생성자를 호출할 수 있다.
class MyButton : View {
constructor(ctx: Context): this(ctx, MY_STYLE) { ... }
constructor(ctx: Context, attr: AttributeSet)
: super(ctx, attr) { ... }
}
4.2.3 인터페이스에 선언된 프로퍼티 구현
코틀린에서는 인터페이스에 추상 프로퍼티 선언을 넣을 수 있다.
interface user {
val nickname: String
}
이 인터페이스를 구현하는 몇 가지 방법을 살펴보자. PrivateUser는 별명을 저장하기만 하고 SubscribingUser는 이메일을 함께 저장한다. FacebookUser는 페이스북 계정의 ID를 저장한다. 이 세 클래스는 각각 다른 방식으로 추상 프로퍼티 nickname을 구현한다.
class PrivateUser(override val nickname: String) : User // 주 생성자에 있는 프로퍼티
class SubscribingUser(val email: String): User {
override val nickname: String
get() = email.substringBefore('@') // 커스텀 getter
}
class FacebookUser(val accountId: Int) : User {
override val nickname = getFacebookName(accountId) // 프로퍼티 초기화 식
}
>>> println(PrivateUser("test@kotlinlang.org").nickname)
test@kotlinlang.org
>>> println(SubscribingUser("test@kotlinlang.org").nickname)
test
인터페이스에는 추상 프로퍼티뿐 아니라 getter와 setter가 있는 프로퍼티를 선언할 수도 있다. 물론 그런 getter와 setter는 뒷받침하는 필드를 참조할 수 없다(인터페이스는 상태를 저장할 수 없으므로).
interface User {
val email: String
val nickname: String
get() = email.substringbefore('@') // 프로퍼티에 뒷받침하는 필드가 없지만 대신 매번 결과를 계산해 돌려준다.
}
4.2.4 getter와 setter에서 뒷받침하는 필드에 접근
위에서는
1. 값을 저장하는 프로퍼티
2. 커스텀 접근자에서 매번 값을 계산하는 프로퍼티
두 가지 유형에 대해 살펴봤다. 이제는 이 두 유형을 조합해서 어떤 값을 저장하되 그 값을 변경하거나 읽을 때마다 정해진 로직을 실행하는 유형의 프로퍼티를 만드는 방법을 살펴보자. 값을 저장하는 동시에 로직을 실행할 수 있게 하기 위해서는 접근자 안에서 프로퍼티를 뒷받침하는 필드에 접근할 수 있어야 한다.
// 뒷받침하는 필드 = 기존 값
class User(val name: String) {
var address: String = "unspecified"
set(value: String) {
println("""
Address was changed for $name:
"$field" -> "$value".""".trimIndent()) // 뒷받침하는 필드값 읽기
field = value // 뒷받침하는 필드 값 변경하기
}
}
>>> val user = User("Alice")
>>> user.address = "Elsenheimerstrasse 47, 80687 Muenchen"
Address was changed for Alice:
"unspecified" -> "Elsenheimerstrasse 47, 80687 Muenchen"
이 코드에서는 커스텀 setter를 정의해서 추가 로직을 실행한다.
접근자의 본문에서는 field라는 특별한 식별자를 통해 뒷받침하는 필드에 접근할 수 있다. getter에서는 field 값을 읽을 수만 있고, 세터에서는 field 값을 읽거나 쓸 수 있다.
4.2.5 접근자의 가시성 변경
접근자의 가시성은 기본적으로 프로퍼티의 가시성과 같다. 하지만 원한다면 get이나 set 앞에 가시성 변경자를 추가해서 접근자의 가시성을 변경할 수 있다.
class LengthCounter {
var counter: Int = 0
private set // 이 클래스 밖에서 이 프로퍼티의 값을 바꿀 수 없다.
fun addWord(word: String) {
counter += word.length
}
}
'Kotlin' 카테고리의 다른 글
[Kotlin in Action] 4.4 object 키워드: 클래스 선언과 인스턴스 생성 (0) | 2025.03.16 |
---|---|
[Kotlin in Action] 4.3 컴파일러가 생성한 메소드: 데이터 클래스와 클래스 위임 (0) | 2025.03.16 |
[Kotlin in Action] 4.1 클래스 계층 정의 (0) | 2025.03.15 |
[Kotlin in Action] 3.6 코드 다듬기: 로컬 함수와 확장 (0) | 2025.03.14 |
[Kotlin in Action] 3.5 문자열과 정규식 다루기 (0) | 2025.03.14 |
댓글