코틀린을 공부하면서 계속 어색했던 부분은 프로퍼티(property), Backing fields 라는 표현이었다.
자바에서 접하던 필드, getter, setter 등을 묶어서 코틀린에서는 프로퍼티라고 부르는데, 애매하게 알고 넘어갔더니 뒤로 갈수록 꼬이게 됐다..
왜 필드처럼 생겨가지고는
먼저 프로퍼티와 custom 접근자에 대해 간단히 알아보고, Backing fields 에 대해 알아보자.
Backing fields 가 뭘까
class HttpResponse(val body: String, var headers: Map<String, String>)
간단하게 예시 클래스를 만들어보자. body
와 headers
라는 프로퍼티를 가지고 있는 클래스이다.
코틀린을 찍먹해본 사람이라면 알듯이, 코틀린 컴파일러는 body
프로퍼티에 대해서는 getter 를, headers
프로퍼티에 대해서는 getter, setter 를 생성해준다. 그리고 내부적으로 코틀린은 자바로 치면 클래스 내부의 필드를 사용해서 각각의 속성 값을 저장한다. 자바로 디컴파일해보면 아래와 같이 나오는 것을 볼 수 있다.
public final class HttpResponse {
@NotNull
private final String body;
@NotNull
private Map headers;
@NotNull
public final String getBody() {
return this.body;
}
@NotNull
public final Map getHeaders() {
return this.headers;
}
public final void setHeaders(@NotNull Map var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.headers = var1;
}
}
- 꿀팁💡 IntelliJ 에서 Kotlin 디컴파일 하는 법 : Tools > Kotlin > Show Kotlin Bytecode
이렇게 자바 코드로 확인해보면 body
와 headers
에 대해 필드와 getter, setter 가 알맞게 만들어진 것을 확인할 수 있다.
그럼 이번에는 코틀린에서 제공하는 custom 접근자를 만들어보자.
여기서는 body
가 있는지를 알려주는 hasBody
라는 간단한 getter 를 만들어보았다.
class HttpResponse(val body: String, var headers: Map<String, String>) {
val hasBody: Boolean
get() = body.isNotBlank()
}
자바로 디컴파일해보면 아래처럼 메소드가 하나 만들어진 게 보인다.
public final boolean getHasBody() {
CharSequence var1 = (CharSequence)this.body;
return !StringsKt.isBlank(var1);
}
그런데, 아무리 찾아봐도 hasBody
라는 필드는 보이지 않는다. hasBody
라는 프로퍼티는 body
프로퍼티에서 계산해낼 수 있기 때문에 필드를 만들지 않은 것이다. 그렇다면 이번엔 statusCode
라는 프로퍼티를 만들어보자. 주어진 값이 100~599 사이인 경우에만 statusCode
프로퍼티 값을 설정하도록 custom setter 를 만들었다.
var statusCode: Int = 100
set(value) {
if (value in 100..599) statusCode = value
}
자 custom setter 를 잘(?) 만들었으니 또다시 자바로 디컴파일해보자.
private int statusCode;
...
public final void setStatusCode(int value) {
if (100 <= value) {
if (599 >= value) {
this.setStatusCode(value);
}
}
}
이제 문제점이 눈에 보인다 🫠 statusCode
프로퍼티에 값을 set 할 때 custom setter 를 호출하고, setter 안에서 다시 setter 를 호출하는,, 무한 재귀에 빠지게 된다.
물론 IntelliJ 가 warning 띄워줌
이런 무한 재귀를 피하기 위해서는 코틀린이 프로퍼티에 대해 만들어낸 필드 즉, Backing fields를 사용해야 한다.
Backing fields 란 프로퍼티의 값이 저장되는 곳, 자바로 치면 클래스 내의 필드이다.
위 자바 바이트 코드에서 보이듯이 private int statusCode;
이게 바로 Backing fields.
var statusCode: Int = 100
set(value) {
if (value in 100..599) field = value
}
위 코드처럼 field
식별자를 이용하면 custom 접근자 내부의 field
를 참조할 수 있다. 이렇게 field 식별자를 통해서 Backing field 를 참조하면 무한재귀 set 을 막을 수 있다. 아래 자바 바이트코드에서도 우리가 예상한대로 statusCode
필드에 값이 잘 들어가는 것을 확인할 수 있다.
public final void setStatusCode(int value) {
if (100 <= value) {
if (599 >= value) {
this.statusCode = value;
}
}
}
그럼 Backing fields 는 언제 생길까
코틀린은 하나 이상의 default 접근자를 사용하거나, custom 접근자 안에서 field 식별자를 참조하는 경우 Backing fields 를 만든다.
다시 한번 위 코드에 대한 자바 바이트코드에서 Backing field 로 어떤 것들이 생겨났는지 살펴보자.
public final class HttpResponse {
private int statusCode;
@NotNull
private final String body;
@NotNull
private Map headers;
...
}
- body 프로퍼티는 default 접근자에 의해서 backing field 가 생겨났다.
- headers 프로퍼티도 동일
- statusCode 프로퍼티는 field 식별자를 참조해서 생겨났다.
- hasBody 프로퍼티는 default 접근자를 사용하지도, field 식별자를 참조하지도 않기 때문에 backing field 가 생겨나지 않았다
끝.
레퍼런스
- Kotlin In Action
- https://www.baeldung.com/kotlin/backing-fields