apply 스코프 내부에 레이블 없이 사용된 리시버는 create()메서드의 반환값을 가리킨다. (this?.name) 그리고 외부 리시버를 사용하기 위해 레이블을 붙여 사용하는 것을 확인할 수 있다. (this@Node.name)
출력: Created parent-child in parent
어떤 리시버를 활용하는지 의미를 명확하게 하면 코드의 안정성과 가독성을 향상할 수 있다. 리시버를 제거하여 코드를 짧게 만든다고 해서 무조건 좋은 것은 아니다.
📌[Item 16] 프로퍼티는 동작이 아니라 상태를 나타내야 한다.
✅ 코틀린 프로퍼티
코틀린의 프로퍼티와 자바의 필드는 데이터를 저장한다는 점은 동일하다.
코틀린 프로퍼티는 사용자 정의 세터와 게터를 가질 수 있다.
코틀린 프로퍼티는 접근자로 val 은 get( )만 / var 은 get(), set() 모두 정의할 수 있다.
프로퍼티는 필드가 필요 없다. 프로퍼티는 개념적으로 접근자를 나타낸다. 그러므로 인터페이스에서도 프로퍼티를 정의할 수 있다.
✅ 코틀린 프로퍼티 get( ), set() 예시
class User {
var name: String = "unknown"
get() = field.capitalize() // 게터 정의: 이름을 대문자로 변환하여 반환
set(value) {
field = value.trim() // 세터 정의: 입력받은 값을 공백 제거 후 저장
}
var age: Int = 0
get() = field
set(value) {
if (value >= 0) field = value // 세터 정의: 나이는 0 이상이어야 한다.
}
}
fun main() {
val user = User()
user.name = " john doe " // 세터를 통해 공백이 제거되고 저장됨
println(user.name) // 출력: "John Doe" (게터를 통해 대문자로 변환되어 출력됨)
user.age = -5 // 나이에 음수를 설정하려고 하면 세터에서 거부됨
println(user.age) // 출력: 0 (음수가 설정되지 않음)
user.age = 30 // 올바른 나이 설정
println(user.age) // 출력: 30
}
위 예시처럼 코틀린 프로퍼티는 디폴트로 캡슐화되어 있습니다. 만약 User 클래스의 age가 프로젝트 전역에서 사용되고 있다고 가정하겠습니다. age의 타입을 String으로 변경해야 한다면 사용되는 모든 곳을 직접 변경하지 않고 게터와 세터를 변경해서 해결할 수 있습니다.
// Int 타입의 값을 받아서 String으로 변환하여 저장하는 setter
var ageAsInt: Int
get() = age.toIntOrNull() ?: 0 // String을 Int로 변환하여 반환. 변환 실패 시 0 반환
set(value) {
age = value.toString() // Int 값을 String으로 변환하여 age에 저장
}
✅ 코틀린 프로퍼티 위임
Kotlin에서 프로퍼티 위임(Property Delegation)은 특정 작업을 다른 클래스에 위임함으로써 코드를 더 깔끔하게 관리할 수 있게 해 줍니다.
import kotlin.reflect.KProperty
class DelegatedIntProperty(var value: Int) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
println("${property.name} 프로퍼티의 get() 호출")
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: Int) {
println("${property.name} 프로퍼티의 set() 호출, 새로운 값: $newValue")
value = newValue
}
}
class User {
var age: Int by DelegatedIntProperty(0)
}
fun main() {
val user = User()
println(user.age) // age 프로퍼티의 get() 호출
user.age = 25 // age 프로퍼티의 set() 호출, 새로운 값: 25
println(user.age) // age 프로퍼티의 get() 호출
}
✅ 프로퍼티의 게터, 세터에 알고리즘 동작을 넣는 것은 좋지 않다.
게터와 세터에 when, if 등 여러 분기 또는 알고리즘 동작을 하는 로직은 해당 프로퍼티에 대한 오해를 발생시킬 가능성이 높아집니다. 또한 만약 크기가 큰 컬렉션에 데이터를 읽고 쓸 경우 더 많은 계산을 필요로 하게 됩니다. 즉, 이러한 로직은 함수로 구현하는 것이 바람직합니다.
프로퍼티 게터. 세터 대신 함수를 사용하기 좋은 경우
연산 비용이 높거나, 복잡도가 O(1)보다 큰 경우 관습적으로 프로퍼티를 사 용할 때 연산 비용이 많이 필요하다고 생각하지 않습니다. 연산 비용이 많 이 들어간다면, 함수를 사용하는 것이 좋습니다. 그래야 사용자가 연산 비 용을 예측하기 쉽고, 이를 기반으로 캐싱 등을 고려할 수 있기 때문입니다.
비즈니스 로직(애플리케이션의 동작)을 포함하는 경우 관습적으로 코드를 읽을 때 프로퍼티가 로깅, 리스너 통지, 바인드된 요소 변경과 같은 단순한 동작 이상을 할 거라고 기대하지 않습니다.
결정적이지 않은 경우 같은 동작을 연속적으로 두 번 했는데 다른 값이 나 올 수 있다면, 함수를 사용하는 것이 좋습니다.
변환의 경우 변환은 관습적으로 Int.toDouble()과 같은 변환 함수로 이루어집니다. 따라서 이러한 변환을 프로퍼티로 만들면, 오해를 불러일으킬 수 있습니다.
게터에서 프로피디의 상태 변경이 일어나야 하는 경우 관습적으로 게터에서 프로퍼티의 상태 변화를 일으킨다고 생각하지는 않습니다. 따라서 게터에서 프로퍼티의 상태 변화를 일으킨다면, 함수를 사용하는 것이 좋습니다.