부 생성자
생성자가 여럿 필요한 경우가 가끔 있다.
가장 일반적인 상황은 프레임워크 클래스를 확장해야 하는데 여러 가지 방법으로 인스턴스를 초기화 할 수 있게 다양한 생성자를 지원해야 하는 경우다.
예시
View 클래스가 있다. 그 클래스를 코틀린으로 코드를 작성하면
open class View {
constructor(ctx : Context) {
...
}
construcotr(ctx : Context, attr : AttributeSet) {
....
}
}
위 코드로 작성할 수 있다. 해당 클래스는 주 새성자를 선언하지 않고 부 생성자만 2개 선언했다.
부 생성자는 constructor 키워드로 시작한다.
위 클래스를 확장하면서 똑같이 부 생성자를 정의할 수 있다.
class MyButton : View {
constructor(ctx : Context) : super(ctx) {
...
}
constructor(ctx : Context, attr : AttributeSet) : super(ctx, attr) {
...
}
}
여기서 두 부 생성자는 super() 키워드를 통해 자신에 대응하는 상위 클래스 생성자를 호출한다.
또한, 자바와 마찬가지로 생성자에서 this() 를 통해 클래스 자신의 다른 생성자를 호출할 수 있다.
class MyButton : View {
constructor(ctx : Context) : this(ctx, MY_STYLE) {
...
}
constructor(ctx : Context, attr : AttributSet) : super(ctx, arrt) {
...
}
}
MyButton 클래스의 생성자 중 하나가 파라미터의 디폴트 값을 넘겨서 같은 클래스의 다른 생성자(this를 사용하는..)에게 생성을 위임한다.
클래스에 주 생성자가 없다면 모든 부 생성자는 반드시 상위 클래스를 초기화하거나 다른 생성자에게 생성을 위임해야 한다.
부 생성자가 필요한 주된 이유는 자바 상호운용성이다.
하지만, 부 생성자가 필요한 다른 경우도 있다.
클래스 인스턴스를 생성할 때 파라미터 목록이 다른 생성 밥이 여럿 존재하는 경우, 부 생성자를 여럿 둘 수 밖에 없다.
인터페이스에 선언된 프로퍼티 구현
코틀린에서는 인터페이스에 추상 프로퍼티 선언을 넣을 수 있다.
interface User {
val nickname : String
}
인터페이스에 있는 프로퍼티 선언엔 뒷받침하는 필드나 게터 등의 정보가 들어있지 않다.
따라서, 인터페이스는 아무 상태도 포함할 수 없으므로 상태를 저장할 필요가 있으며,
인터페이스를 구현한 하위 클래스에 상태 저장을 위한 프로퍼티 등을 만들어야 한다.
아래는 인터페이스를 구현하는 클래스의 각기 다른 방법이다.
// 주 생성자에있는 프로퍼티
class PrivateUser(override val nickname: String) : User
// 커스텀 게터 (nickname은 매번 호출될 때마다 substringBefore를 호출해 계산)
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 : 주 생성자 안에 프로퍼티를 직접 선언하는 간결한 구문 사용. 이 클래스 프로퍼티는 User 의 추상 프로퍼티를 구현하고 있으므로, override 를 표시해야 한다.
SubscribingUser : 커스텀 게터로 nickname 프로퍼티를 설정한다. 이 프로퍼티는 뒷받침하는 필드에 값을 저장하지 않고, 매번 이메일 주소에서 별명을 계산해서 반환한다.
FacebookUser : 초기화 식으로 nickname 값을 초기화 한다. 페이스북 사용자 ID를 받아서 그 사용자의 이름을 반환해주는 함수를 호출 해서 nickname 을 초기화 한다. (이 부분은 페이스북에서 접속해서 인증을 거친 후 원하는 데이터를 가져와야하기 때문에 비용이 많이 들 수 있다. 그래서 객체를 초기화하는 단계에 한 번만 getFacebookName 을 호출하게 설계한 것이다)
인터페이스 에는 추상 프로퍼티뿐 아니라, 게터와 세터가 있는 프로퍼티를 선언할 수 있다.
물론 그런 게터와 세터는 뒷받침하는 필드를 참조할 수 없다.
interface User {
val email : String,
val nickname : String
get() = email.substringBefore('@') // 프로퍼티에 뒷받침하는 필드가 없다. 대신 매번 결과를 계산해서 돌려준다.
}
만약 위 인터페이스를 구현하는 하위 클래스는 추상 프로퍼티인 email 을 반드시 오버라이드해야 한다. 반면 nickname 은 오버라이드 하지 않고 상속할 수 있다.
그리고 역시나, 인터페이스에 선언된 프로퍼티와 달리 클래스에 구현된 프로퍼티 프로퍼티는 뒷받침하는 필드를 원하는대로 사용가능하다.
게터와 세터에서 뒷받침하는 필드에 접근
프로퍼티의 두 가지 유형이 있다.
- 값을 저장하는 프로퍼티
- 커스텀 접근자에서 매번 값을 계산하는 프로퍼티
// 세터에서 뒷받침하는 필드 접근하기
fun main(args : Array<String>) {
val user = User("chrisAn")
user.address = "서울 강남구 대치동" // 내부적으로 세터를 호출
}
class User(val name : String) {
var address : String = "unspecified"
set(value : String) {
println("Address was changed for $name")
println("$field" + " => " + "$value")
field = value
}
}
접근자의 가시성 변경
class LengthCounter {
val counter : Inte = 0
private set // 외부에 공개하고싶지 않을 때, 가시성을 private 으로 지정.
fun addWord(word : String) {
counter += word.length
}
}
'Language > Kotlin' 카테고리의 다른 글
[Kotlin] zip(), mapIndexed(), filterNotNull() (0) | 2023.07.09 |
---|---|
[Kotlin IN ACTION] - Kotlin 클래스 선언(주 생성자, 프로퍼티) (0) | 2022.07.25 |
[Kotlin IN ACTION] - Kotlin sealed 클래스 (0) | 2022.07.25 |
[Kotlin IN ACTION] - Kotlin 접근 제어자(가시성 변경자) / 내부 클래스 / 중첩 클래스 (0) | 2022.07.25 |
[Kotlin IN ACTION] - Kotlin open / final / abstract 상속 제어(접근) 변경자 (0) | 2022.07.22 |