Kotlin in Action 을 공부하고 Effective kotlin 의 내용을 조금 참조하여 정리한 글입니다.
인터페이스에 선언된 프로퍼티
코틀린에서는 인터페이스에 추상 프로퍼티 선언을 넣을 수 있다.
interface User {
val nickname: String
}
`User` 인터페이스를 구현하는 클래스는 `nickname` 의 값을 얻을 수 있는 방법을 제공해야 한다.
인터페이스에 있는 프로퍼티 선언에는 어떠한 상태를 포함할 수 없다.
즉, backing field 에 대한 정보가 들어있지 않다.
프로퍼티 선언을 가진 인터페이스 구현
인터페이스를 구현하는 방법을 코드로 보자
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: String = getFacebookName(accountId))
}
fun getFacebookName(accountId: Int): String {
// 페이스북 접속, 인증, 원하는 데이터를 리턴하는 코드라고 가정
return "sh1mj1"
}
모두 `nickname` 프로퍼티를 `override` 하고 있다.
backing field 를 갖도록 하거나, getter 접근자를 구현해주고 있다.
- `PrivateUser`
- 주 생성자 안에 프로퍼티를 직접 선언하는 간결한 구문 사용
- `SubscribingUser`
- 커스텀 getter 로 추상 프로퍼티를 구현
- backing field 에 값을 저장하지 않고 매번 `email` 에서 `nickname` 을 계산하여 리턴
- `FacebookUser`
- initializer(초기화 식)으로 `nickname` 을 초기화.
- 객체를 초기화하는 단계에서 한번만 `getFacebookName` 을 호출하도록 설계함
(계산한 데이터를 backing field 에 저장했다가 불러오는 방식).
테스트 코드
@Test
fun testPrivateUser() {
val privateUser = PrivateUser("sh1mj1")
assert(privateUser.nickname == "sh1mj1")
}
@Test
fun testSubscribingUser() {
val subscriber = SubscribingUser("sh1mj1@kotlinlang.org")
assert(subscriber.nickname == "sh1mj1")
}
@Test
fun testFacebookUser() {
val facebookUser = FacebookUser(9938)
assert(facebookUser.nickname == "sh1mj1")
}
인터페이스에도 getter & setter 가 있는 프로퍼티 선언 가능
인터페이스에는 추상 프로퍼티뿐 아니라 getter 와 setter 가 있는 프로퍼티를 선언할 수 있다.
물론 그런 getter, setter 는 backing field 를 참조할 수 없다.
위에서 말했듯 인터페이스는 상태를 가질 수 없다!
interface User2 {
val email: String
val nickname: String
get() = email.substringBefore('@')
}
이 인터페이스를 구현하는 클래스에서는 추상 프로퍼티인 `email` 을 반드시 `override` 해야 한다.
반면에 `nickname` 은 `override` 하지 않아도 된다.
class SubscribingUser2(override val email: String) : User2
@Test
fun testSubscribingUser() {
val subscribingUser2 = SubscribingUser2("sh1mj1@kotlinlang.org")
assert(subscribingUser2.nickname == "sh1mj1")
}
getter 와 setter 에서 backing field 에 접근
위에서 프로퍼티는 값을 저장하는 프로퍼티 형식과 커스텀 접근자(getter,setter)에서 매번 값을 계산하는 프로퍼티 형식에 대해 살펴보았다.
이제는 두 형식을 조합해서 어떤 값을 저장하되, 그 값을 write 하거나 read 할 때마다 정해진 로직을 실행하는 유형의 프로퍼티를 만들어보자.
값을 저장하는 동시에 로직을 실행하려면 접근자 안에서 프로퍼티의 backing field 에 접근할 수 있어야 한다.
프로퍼티에 대해 set 할 때, 즉 프로퍼티 값을 바꿀 때마다 프로퍼티에 저장된 값의 변경 사항을 출력하고자 한다.
class User(val name: String) {
var address: String = "unspecified"
set(value: String) {
println(
"""
Address was changed for $name:
"$field" -> "$value".
""".trimIndent()
)
field = value
}
}
위 코드에서처럼 접근자의 바디에서 `field` 라는 특별한 키워드를 통해 backing field 에 접근할 수 있다.
getter 에서는 `field` 값을 읽을 수만 있꼬, setter 에서는 `field` 값을 읽고, 쓸 수 있다.
테스트 코드
@Test
fun testUser() {
val user = User("sh1mj1")
user.address = "동대문구 서울"
}
// printed
/*
Address was changed for sh1mj1:
"unspecified" -> "동대문구 서울".
*/
`var` 프로퍼티의 getter 와 setter 중 하나만 직접 정의해도 된다.
위 코드의 `address` 의 getter 는 필드 값을 그냥 리턴해주는 기본 getter 이다.
컴파일러는 디폴트 접근자를 사용하던지, 혹은 직접 getter 나 setter 를 정의하던지에 관계없이 getter 나 setter 에서 field 를 사용하는 프로퍼티에 대해 backing field 를 생성해준다.
하지만 `field` 를 사용하지 않는 커스텀 접근자만을 사용하는 프로퍼티에 대해서는 backing field 는 생성되지 않는다.
접근자의 가시성
접근자의 가시성은 기본적으로 프로퍼티의 가시성과 같다.
하지만 원한다면 get 이나 set 앞에 가시성 변경자를 추가해서 접근자의 가시성을 변경할 수 있다.
class LengthCounter {
var counter: Int = 0
private set
fun addWord(word: String) {
counter += word.length
}
}
이렇게 하면 `counter` 는 이 클래스 내에서만 값을 변경할 수 있다.(set)
그리고 `counter` 의 가시성은 public 이므로 클래스 외부에서도 읽을 수는 있다(get).
직접 테스트해보자.
@Test
fun testLengthCounter(){
val lengthCounter = LengthCounter()
lengthCounter.addWord("Hi!")
assert(lengthCounter.counter == 3)
// lengthCounter.counter = 3 // Cannot assign to 'counter': the setter is private in 'LengthCounter'
}
실제로 `counter` 를 테스트 코드에서 get 할 수 있지만, set 할 수 없는 것을 확인할 수 있다.
지금까지의 내용이 프로퍼티에 대해 모든 것은 아니다.
lateinit 변경자를 프로퍼티에 지정할 때, 또 delegated property(위임 프로퍼티) 등은 또 나중에 다루겠다.
'Kotlin' 카테고리의 다른 글
[Kotlin] data class: toString, equals, hashCode, copy, componentN (0) | 2024.01.10 |
---|---|
[Kotlin] 모든 클래스가 정의해야 하는 메서드 toString, equals, hashCode (1) | 2024.01.09 |
[Kotlin] 주 생성자 & 부 생성자 & 초기화 블록 (1) | 2024.01.06 |
[Kotlin] sealed 로 클래스 계층 확장을 제한하기 + 태그 클래스 VS 상태 패턴 (1) | 2024.01.06 |
[Kotlin] inner class & nested class 자바와 비교해서 (feat. 직렬화) (1) | 2024.01.06 |