본문 바로가기
공부/이펙티브코틀린

아이템 16 - 프로퍼티는 동작이 아니라 상태를 나타내야 한다

by 띵커베르 2024. 4. 8.
728x90
  • 코틀린의 프로퍼티는 자바의 필드와 비슷해 보이지만, 사실 서로 완전히 다른 개념입니다.
  • 둘 다 데이터를 저장한다는 점은 같습니다. 하지만 프로퍼티에는 더 많은 기능이 있습니다.
  • 일단 기본적으로 프로퍼티는 사용자 정의 세터와 게터를 가질 수 있습니다.
class User {
    var name: String = "initial name"
        get() = field // getter에서 백킹 필드에 접근
        set(value) {
            field = value // setter에서 백킹 필드에 값을 할당
        }
}
  • 위 코드에서 setter 에 name = value 를 하지않지 않고 field를 사용한 이유와 백킹필드란?
    • 백킹필드
      • 프로퍼티의 값을 저장하는 데 사용되는 내부적인 필드
      • 코틀린에서는 프로퍼티의 값을 저장하고 검색할 필요가 있을 때만 백킹 필드를 생성한다.
      • 프로퍼티에 직접 접근하거나 값을 할당하는 대신 field 식별자를 통해 이 백킹 필드에 접근하고 값을 할당하거나 검색할 수 있다.
    • setter 에 filed 를 사용한 이유
      • name = value 와 같이 프로퍼티 자체에 값을 할당하려고 하면, 실제로는 프로퍼티의 setter 함수가 재귀적으로 호출되어 스팩오버플로 에러를 발생한다.
      • name = value 는 setName(value) 메서드를 호출하는 것과 동일한 작업을 수행하기 때문에 setter 내에서 프로퍼티 이름을 사용하여 값을 할당하면 무한 재귀 호출이 발생한다

 

  • field 를 사용하면 이러한 문제 없이 안전하게 백킹 필드에 직접 값을 할당하거나 검색할 수 있다
  • field 식별자는 오직 프로퍼티의 getter setter 내에서만 사용할 수 있으며, 이는 프로퍼티의 백킹 필드에 접근하기 위한 안전한 방법을 제공한다.
  • 자바는 setName 으로 되는데요?
    • 언어의 설계 철학과 구현 메커니즘에 기반한다
    • 자바에서는 필드에 접근하는것을 피하고 캡슐화를 장려하기 위해, getter setter 메서드를 명시적으로 작성하여, setName 메서드를 사용하여 필드의 값을 설정할 때, 해당 메서드는 특정 필드의 값에 할당하는 명확하게 정의된 로직을 포함한다
      • this.name = name; // 직접 필드에 값을 할당
    • 코틀린에서는 프로퍼티의 구현을 간결하기 만들기 위해 자동으로 getter setter 를 제공하고, 프로퍼티에 값을 할당할 때 field 식별자를 사용하면, 코틀린 컴파일러는 이를 백킹 필드에 값을 할당하는 것으로 인식한다
    • field 는 프로퍼티의 자동 생성된 getter, setter 내에서만 사용할 수 있는 특별한 식별자이다.
  • 정리 ------------->
    • 즉 자바에서는 getter 와 setter 메서드를 통해 필드의 캡슐화를 직접 관리한다.setName 메서드에서 필드에 값을 할당하는 것은 직접적이고 명시적인 방법이다.
    • 코틀린에서는 프로퍼티의 getter 와 setter 를 자동으로 생성하고 field 식별자를 사용하여 백킹 필드에 안전하게 접근할 수 있다. 이는 프로퍼티의 값을 설정할 때 재귀 호출을 방지하는 안전한 방법이다.
  • 위 코드에서 field 라는 식별자를 확인할 수 있습니다.
    이는 프로퍼티의 데이터를 저장해 두는 백킹 필드에 대한 레퍼런스 입니다.
    이러한 백킹 필드는 게터와 게터의 디폴트 구현에 사용되므로, 따로 만들지 않아도 디폴트로 생성됩니다.
    참고로 val 을 사용해서 읽기 전용 프로퍼티를 만들 때는 field 가 만들어지지 않습니다.
    var 을 사용해서 만든 읽고 쓸 수 있는 프로퍼티는 게터와 세터를 정의할 수 있습니다.
    이러한 프로퍼티를 파생 프로퍼티라고 부르며, 자주 사용됩니다.
class ReadOnlyClass {
	// val 에는 field 를 사용못하지만 해당 초기값을 제공하는 경우 백킹 필드에 저장되어 사용할 수 있다
    val readOnlyProperty: Int = 10
        get() = field

    val readOnlyName: String
        get() = name
}

 

  • 파생 프로퍼티
    • - 기본적으로 다른 프로퍼티의 값에 기반하여 그 값이 결정되는 프로퍼티
// 파생프로퍼티 예시 2가지

class Rectangle(var width: Int, var height: Int) {
    var area: Int
        get() = width * height
        set(value) {
            width = value / height
        }
}

class Person(val firstName: String, val lastName: String) {
    // 파생 프로퍼티: firstName과 lastName에 기반한 fullName
    val fullName: String
        get() = "$firstName $lastName"
}

 

  • 원칙적으로 프로퍼티는 상태를 나타내거나 설정하기 위한 목적으로만 사용하는 것이 좋고, 다른 로직 등을 포함하지 않아야 한다.
  • 이 프로퍼티를 함수로 정의할 경우 접두사로 get 또는 set 을 붙일 것인가? 만약 아니라면 이를 프로퍼티로 만드는 것은 좋지 않다
  • 즉 함수로 사용하는 것이 좋다 함수를 사용하는 것이 좋은 경우는 다음과 같다
    • 연산 비용이 높거나, 복잡도가 O(1) 보다 큰경우, 연산 비용이 크다면 함수를 사용하는 것이 좋ㄷ가
    • 비즈니스 로직을 포함하는 경우
    • 같은 동작을 연속적으로 두 번 했는데 다른 값이 나올 수 있다경우
    • 변환의 경우
    • 게터에서 프로퍼티의 상태 변경이 일어나야 하는 경우
  • 상태를 추출/설정할 때는 프로퍼티를 사용해야 한다. 특별한 이유가 없다면 함수를 사용하면 안 됩니다.

 


생각:

  • 지금처럼 프로퍼티에는 상태를 담고, 함수를 통해 값을 변경하고 하면 될 듯 하다.
  • 자바와다른 코틀린 에서의 백킹필드를 다시한번 생각해보게 되었다.
728x90

댓글