Field 필드 / Property 프로퍼티

자바에서는 필드와 접근자 메서드를 묶어 프로퍼티라고 지칭한다. 코틀린에서는 필드에 대한 기본 접근자 메서드를 자동으로 만들어주기 때문에 필드 대신 프로퍼티라는 말을 사용한다.

Backing Field

  • 프로퍼티의 값을 메모리에 저장하기 위한 필드
  • 대부분의 프로퍼티에는 backing field 가 존재하지만 원한다면 프로퍼티 값을 메모리에 저장하지 않고 바로바로 계산하도록도 할 수 있다
  • 프로퍼티를 선언해줄 때 다음 조건을 만족시킨다면 (메모리에 저장할 필요가 있어지기 때문에) 자동으로 backing field 가 생김
    • 적어도 하나의 접근자가 기본으로 구현되는 접근자를 사용하는 경우 
    • 커스텀 접근자가 field 키워드를 통해 backing field 를 참조하는 경우
// 기본 접근자 사용
val counter = 0 // 커스텀 get() 구현 x

// field 키워드 사용
var counter = 0 // the initializer assigns the backing field directly
    set(value) {
        if (value >= 0)
            field = value
            // counter = value // ERROR StackOverflow: Using actual name 'counter' would make setter recursive
    }
  • backing field 가 없는 경우 예시
val isEmpty: Boolean
    get() = this.size == 0

Backing Properties 를 그럼 언제 쓸까?

  • backing field 는 getter를 통해 반환되는 값이 항상 프로퍼티의 타입과 같아야한다는 제약이 있다.
  • 예를 들어 만약에 내부적으로는 mutableList 이지만 외부적으로 반환할 때는 immutableList 를 반환하고 싶다면? backing property 를 사용하자. 내부적으로는 mutableList 를 저장하고 있고 커스텀 getter 에 immutable 를 반환하도록 하면 가능하다.
  • 즉 backing field 를 사용함에 제약이 걸린다면 backing property 를 사용하여 따로 값을 저장하도록 한다.
  • backing property 는 언더스코어 _를 사용한다
private var _table: Map<String, Int>? = null // backing property
public val table: Map<String, Int>
    get() {
        if (_table == null) {
            _table = HashMap() // Type parameters are inferred
        }
        return _table ?: throw AssertionError("Set to null by another thread")
    }

lateinit

프로퍼티가 non-null이지만 dependency injection 등의 이유로 초기화가 늦어지는 경우 null 타입으로 선언하기는 부담스럽다면 (널 체크가 따라오니까) 사용한다. var 프로퍼티에만 사용가능하며 초기화 전에 접근된다면 특화된 에러를 따로 던져준다.

위임 프로퍼티

class Foo {
	var p: Type by Delegate() // 여기서 Delegate() 인스턴스를 위임 객체 (도우미) 라 함
}

// 컴파일 버젼
class Foo {
	private val delegate = Delegate()
	var p: Type
	set(value: Type) = delegate.setValue(..., value)
	get() = delegate.getValue(...)
}

 

프로퍼티의 초기화를 다른 객체에게 위임하여 초기화하는 것을 위임 프로퍼티라고 한다. 위임 프로퍼티는 실제로 컴파일이 되었을 때는 위임객체의 getter, setter (예시에서는 Delegate() 의 getter, setter) 를 호출한다. 그렇기 때문에 위임 객체 (Delegate) 는 getValue 와 setValue 를 지원해주어야한다.

위임 프로퍼티 이용한 초기화 지연 by lazy()

초기화 지연에 사용하는 lazy가 왜 좋은지 backing property 와 비교해보도록 하자

1. backing property 사용

class Person (val name: String) {
	private var _emails: List<Email>? = null
	val emails: List<Email> 
		get() {
			if (_emails == null) {
				_emails = loadEmails(this)
			}
			return _emails!!
		}
}

val p = Person("kkw")
p.emails // 최초로 emails 를 읽을 때 단 한번만 이메일을 가져옴

이메일이라는 프로퍼티를 초기화하는건 IO 작업이기 때문에 오래걸린다. 따라서 Person 를 처음 인스턴스화 했을 때 아직까진 emails 프로퍼티는 null 상태일 수 밖에 없다. null 로 두면 null check 가 항상 따라다니기 때문에 불편함이 많은데 이를 backing property 로 우선 해결보도록 하자.

방법은 프로퍼티 자체는 non-null 로 선언해두고 getter 에 이메일 초기화 로직을 넣어두는 것이다. 이때 backing property 는 null 로 두어 처음에는 null 이었다가 최초 초기화 이후에는 항상 email 를 지니고 있도록 한다.

  • backing property 는 thread-safe 하지 않다는 문제점 존재
  • backing property 를 사용하면 getter 에 초기화하는 로직을 사용하게 된다.

2. 위임 프로퍼티 사용

class Person(val name: String) {
	val emails by lazy { loadEmails(this) }
}
  • lazy 는 코틀린 관례에 맞는 시그니처의 getValue 메서드가 들어있는 개체를 반환
  • lazy 를 by와 함께 사용하여 위임 프로퍼티 만들 수 있음
  • 오직 한번만 초기화됨을 보장하며 thread safe 함
val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main() {
    println(lazyValue) // computed! hello
    println(lazyValue) // hello
}

lazy를 사용하게 되면 좋은 점은 한번의 초기화를 보장함이다. by lazy 를 통해 값을 초기화한 이후에는 값 초기화 로직 없이 바로 값을 반환하도록 한다.

데이터베이스 example

object Users: IdTable() { // 데이터베이스 테이블
	val name = varchar("name", length=50).index() // 칼럼
	val age = integer("age")
}

class User(id: EntityID): Entity(id) {
	var name: String by Users.name
	var age: Int By Users.age
    // age 초기화가 function 필요하다면 var age: Int By lazy { loadAge(...) }
}

데이터베이스에서 User 객체가 호출될 때 공통적으로 데이터베이스에서 일어나야될 로직을 위임 프로퍼티를 통해 사용 가능하다. (왜냐하면 위임 프로퍼티의 setter, getter 가 불려지기 때문) 따라서 위임 프로퍼티에 공통적으로 처리해야될 로직을 넣는다면 위임받은 객체에서 따로 처리할 필요없이 편하게 사용가능하다. 

 

참고

https://colour-my-memories-blue.tistory.com/6

 

[Kotlin] Backing Field와 Backing Properties (wordle 미션)

자바의 Property란? 자바에서는 필드와 접근자 메서드(getter, setter)를 묶어 property라 한다. 프로퍼티라는 개념이 생긴 이유는 데이터를 캡슐화하려는 자바 클래스의 목적과 연관이 있다. 자바 클래

colour-my-memories-blue.tistory.com

https://kotlinlang.org/docs/properties.html#delegated-properties