본문은 Effective Kotlin을 읽고 간단하게 정리한 글입니다. 필요에 따라 생략/수정된 부분이 있을 수 있으며, 내용이 추후 변경될 수 있습니다.
1. 코틀린의 프로퍼티 vs 자바의 필드
둘 다 데이터를 저장한다는 공통점이 있지만, 다음과 같은 차이가 있다.
1) 자바 필드는 "데이터" 자체를 의미한다.
데이터를 실제로 저장하거나 접근하기 위해선 getter와 setter를 추가적으로 선언해줘야 한다.
2) 프로퍼티는 개념적으로 접근자를 나타낸다.
추가적으로 프로퍼티는 필드보다 확장된 개념, 즉 프로퍼티 = 필드(Optional) + 접근자이다.
val의 경우 getter, var의 경우 getter와 setter를 나타낸다. 이러한 이유로 인터페이스에서도 다음과 같이 프로퍼티를 정의할 수 있다.
interface Person {
val name: String
}
// decompiled source
public interface Person {
@NotNull
String getName();
}
자세히 보면 값을 초기화하지 않는다. 값을 초기화한다는 것은 필드를 만드는 것이기 때문이다. 실제 구현체를 만들고 디컴파일 해보면 다음과 같은 결과를 확인할 수 있다.
class PersonImpl : Person {
override val name: String = "john"
}
// decompiled source
public final class PersonImpl implements Person {
@NotNull
private final String name = "john";
@NotNull
public String getName() {
return this.name;
}
}
+ 추가적으로 책에서 나온 백킹 필드와 파생 프로퍼티에 대해 짧게 알고 넘어가자.
- 무한 루프를 막기 위한 예약어, 프로퍼티의 값을 가리킨다
- 아래 코드에서 커스텀 getter 내에서 name?.toUpperCase() 처럼 호출하면 getName()?.toUpperCase 와 같은 형태로 호출된다
- 그럼 다시 (getName()?.toUpperCase)?.toUpperCase와 같은 형태가 되면서 무한 루프를 형성하게 된다
- 코틀린은 field라는 예약어를 제공한다
- this를 사용하면 되므로 실제로는 잘 쓰이지 않는다 (아래 코드에선 this.name과 같은 형태로 사용 가능)
var name: String? = null
get() = field?.toUpperCase()
set(value) {
if (!value.isNullOrBlack()) {
field = value
}
}
- 책에서는 val을 사용해서 읽기 전용 프로퍼티를 만들 때는 field가 만들어지지 않는다고 하는데, 실제로는 만들어진다(코틀린 1.8.22 기준). 값 초기화를 하지 않았을 땐 가리킬 값이 없으므로 field를 당연히 사용할 수 없다.
class Test {
val name: String = ""
get() = "Mr. $field" // OK
}
class Test2 {
val name: String // error: Property must be initialized
get() = "Mr. $field"
var varName: String // error: Property must be initialized
get() = "Ms. $field"
}
2) 파생 프로퍼티(derived property)
- 기존의 프로퍼티들로부터 계산되거나 파생된 값을 의미하는 프로퍼티
- 자기 자신의 값을 가지지는 않는다
class Length {
var centimeters: Double = 0.0
val inches: Double // 파생 프로퍼티
get() = centimeters / 2.54
}
2. 프로퍼티 사용 원칙
1) 어떨 때 프로퍼티를 사용해야 하는가?
- 기본적으로 상태를 나타내거나 설정하기 위한 목적으로만 사용하고, 다른 로직 등은 포함하지 않는 것이 좋다.
- 따라서 프로퍼티를 함수라고 가정할 때 접두사로 get/set을 붙일 것 같은 경우에만 프로퍼티로 설정하라.
// Bad - 특별한 이유가 없다면 이렇게 하지 않는 것이 좋다!
class UserIncorrect {
private var name: String = ""
fun getName() = name
fun setName(name: String) {
this.name = name
}
}
// Good - 상태를 추출/설정할 때는 프로퍼티를 사용하라
class UserCorrect {
var name: String = ""
}
2) 프로퍼티 대신 함수를 사용하면 좋은 경우
- 연산 비용이 높거나 복잡도가 O(1)보다 큰 경우
- 비즈니스 로직을 포함하는 경우
- 결정적이지 않은 경우 (= 같은 동작을 연속적으로 두 번 했을 때 다른 값이 나올 수 있는 경우)
- 변환 로직을 담고 있는 경우
- getter에서 프로퍼티의 상태 변경이 일어나야 하는 경우
'책 > Effective Kotlin' 카테고리의 다른 글
[이펙티브 코틀린] 아이템 21: 일반적인 프로퍼티 패턴은 프로퍼티 위임으로 만들어라 (0) | 2023.08.02 |
---|---|
[이펙티브 코틀린] 아이템17: 이름 있는 아규먼트를 사용하라 (0) | 2023.07.23 |
[이펙티브 코틀린] 아이템9: use를 사용하여 리소스를 닫아라 (0) | 2023.07.04 |
[이펙티브 코틀린] 아이템8: 적절하게 null을 처리하라 (0) | 2023.07.04 |
[이펙티브 코틀린] 아이템7: 결과 부족이 발생할 경우 null과 Failure를 사용하라 (0) | 2023.07.04 |