자바의 Property란?
자바에서는 필드와 접근자 메서드(getter, setter)를 묶어 property라 한다.
프로퍼티라는 개념이 생긴 이유는 데이터를 캡슐화하려는 자바 클래스의 목적과 연관이 있다. 자바 클래스는 기본적으로 필드를 private으로 설정하고 외부에서 값을 가져오거나 변경할 때 getter, setter를 제공한다. 이를 통해 캡슐화된 클래스의 고유한 기능은 유지하면서 클라이언트의 요구에 따라 속성 값을 확인하거나 변경할 수 있게 해준다.
코틀린의 Property란?
코틀린에서는 필드에 대한 기본 접근자 메서드를 자동으로 만들어준다. 때문에 필드 대신 프로퍼티라는 말을 사용한다. 원한다면 접근자 메서드를 명시적으로도 선언할 수 있다.
Backing Field
backing field는 프로퍼티의 값을 저장하기 위한 필드다. 코틀린에서는 필드를 바로 선언할 수 없고 프로퍼티로 선언하면 아래의 경우에 자동으로 backing field가 생긴다.
- 적어도 하나의 접근자가 기본으로 구현되는 접근자를 사용하는 경우
- 커스텀 접근자가 field 키워드를 통해 backing field를 참조하는 경우
// 코틀린 코드
class A {
var counter = 0
set(value) {
if (value >= 0) field = value
}
}
위 코드를 보면 counter 프로퍼티를 만들어 0으로 초기값을 설정했다. setter에서는 값을 검증하고 field = value를 통해 counter의 값을 변경한다.
// 자바로 변환한 코드
public final class A {
private int counter;
public final int getCounter() {
return this.counter;
}
public final void setCounter(int value) {
if (value >= 0) {
this.counter = value;
}
}
}
자바로 변환한 코드를 보면 counter 필드가 생성되어 있고 getter와 setter가 따로 생성되었다. 즉, 코틀린의 프로퍼티 값을 저장하기 위해 자바의 필드가 필요하다. 이렇게 프로퍼티 값을 저장하기 위한 필드를 backing field라 부른다.
backing field를 사용하지 않는 경우
// 코틀린 코드
class A {
var size = 0
val isEmpty: Boolean
get() = this.size == 0
}
// 자바로 변환한 코드
public final class A {
private int size;
public final int getSize() {
return this.size;
}
public final void setSize(int var1) {
this.size = var1;
}
public final boolean isEmpty() {
return this.size == 0;
}
}
위의 경우 isEmpty 프로퍼티는 메모리에 아무 값도 저장하지 않는다. size와는 달리 getter만 존재하며 필드가 생성되지 않는다.
field 키워드를 사용하는 이유
field 키워드를 사용하지 않고 setter를 아래와 같이 정의하면 무한 재귀에 빠질 수 있다.
// 코틀린 코드
class A {
var counter = 0
set(value) {
if (value >= 0) counter = value
}
}
// 자바로 변환한 코드
public final class A {
// 생략
public final void setCounter(int value) {
if (value >= 0) {
setCounter(value); // 무한 재귀
}
}
}
그렇다면 Backing Properties란?
backing field에서 필요에 따라 커스텀 getter나 setter를 만드는 경우에도 제약이 존재한다. getter는 반환 타입이 반드시 프로퍼티의 타입과 같아야 하기 때문이다. result를 가변이 아닌 불변 리스트로 넘기고 싶어도 반환 타입이 무조건 MutableList여야하기 때문에 불가능하다.
private var results: MutableList<List<Tile>> = mutableListOf()
private set
이런 경우 아래와 같이 backing properties를 사용하면 result를 불변 리스트로 반환할 수 있다.
private var _results: MutableList<List<Tile>> = mutableListOf()
val results: List<List<Tile>>
get() = _results
- prefix로 언더스코어 _ 를 붙인다.
아래 두 가지 코드의 차이는? - 1. backing field VS properties
// backing field
var table: Map<String, Int>? = null
private set
get() {
if (field == null) {
field = HashMap()
}
return field ?: throw AssertionError("Set to null by another thread")
}
// backing properties
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap()
}
return _table ?: throw AssertionError("Set to null by another thread")
}
두 개의 코드는 근본적으로 다르지 않다. backing properties를 사용했을 때는 backing field를 사용하지 않을 수 있다.
아래 두 가지 코드의 차이는? - 2. 언제 backing properties를 사용할까?
var count: Int = 0
private set
private var _results: MutableList<List<Tile>> = mutableListOf()
val results: List<List<Tile>>
get() = _results
count의 경우 기본 getter로 바로 값을 반환하는 것과, backing properties로 만들어 getter를 직접 명시해주는 것에 차이가 없다. 값을 반환할 때 추가적인 작업이 필요하지 않기 때문이다.
반면 result의 경우 기본 getter로 값을 반환하면 변경가능한 mutableList로 반환된다. 때문에 backing properties를 만들어 내부에서는 값이 변경되지만, getter를 통해 값을 외부로 넘겨줄 때는 불변 List로 변경하여 넘길 필요가 있다. 따라서 backing property를 사용한다.
아래 두 가지 코드의 차이는? - 3. 커스텀 getter VS 기본 setter
private val _results: MutableList<List<Mark>> = mutableListOf()
val results: List<List<Mark>>
get() = _results
private val _results: MutableList<List<Mark>> = mutableListOf()
val results: List<List<Mark>> = _results
결론부터 말하자면 두 가지 방식에 차이가 있다. (지금은 _result가 value라서 상관없겠지만, variable인 경우 차이가 생긴다.)
첫 번째 방식을 사용하면 result를 getter로 가져올 때 _result의 값을 반환해준다. 반면에 두 번째 방식을 사용하면 초기화될 때 result에 _result 값이 세팅되어 버린다. 따라서 중간에 _result의 값이 바뀌는 경우에 변경된 값이 아니라 초기에 세팅된 값을 가져오게 된다. 잘 구분해서 사용하자.
reference
Kotlin 기초강의#5 :: 프로퍼티(Property)와 뒷받침하는 필드(Backing Field)
Properties and Field (프로퍼티, 필드, 접근자)
'코틀린' 카테고리의 다른 글
[Kotlin] mutable collection 사용 줄이기 (wordle 미션) (7) | 2022.05.31 |
---|---|
[Kotlin] 확장함수 (wordle 미션) (0) | 2022.05.31 |
[Kotlin] Sealed Class (wordle 미션) (0) | 2022.05.31 |