backing field, 그리고 이에 따른 property들의 initialization 여부

sycho·2023년 7월 16일
0

cs-tips

목록 보기
5/15

Kotlin 공부 중에 backing field에 관한 내용이 나왔는데, 이에 대해 만족스러울 정도로 분석한 하나의 글이 없어서 직접 분석해본 후 정리해 봤다.

Kotlin getter / setter

간단히 말하자면 class의 property (혹은 attribute)를 접근하거나 읽으려고 할 때 호출되는 함수다.
constructor을 통해 initialize할 때도 호출되고, initialize 이후에 읽거나 재할당을 할 때도 호출된다.
val로 선언된 attribute들은 재할당이 불가능하니 getter만 정의되며, var로 선언된 property들은 재할당이 가능해서 getter, setter이 둘 다 정의된다. 각 property마다 정의가 된다.

혹시 class에 대해 알고 싶다면 이 documentation을 보자. 여기서는 자세히 안 다루겠다.

getter/setter은 각 우리가 직접 작성할 수도 있고, 작성을 하지 않으면 다음과 같이 default getter/setter이 각 property에 대해 만들어진다고 생각하면 된다.

var myProperty: String
	get() = field
	set(value) {
		field = value
	}

이것에 대해 더 자세히 알고 싶으면 geeksforgeeks의 이 글을 봐보자

backing field

위에 대해서도 길게 말할 수 있지만 오늘 자세히 다룰 내용은 바로 backing field, 그리고 이에 따라 결정되는 property들의 initialization 여부에 관한 내용이다.
먼저 backing field에 대해 간략히 알아보자. backing field는 위의 set(value)get()에 있는 field다.
왜 이런 녀석이 갑자기 등장하냐면, 다음과 같은 코드에서 가질 수 있는 문제점을 막기 위해서다.

var height = height_param
	set(value) {
    	if (value > 0) height = value
    }

height가 어떤 class의 property라고 해보자. 그러면 setter을 저렇게 정의한다면 '직관적'으로는 heightvalue라는 값을 value가 0보다 클 때 저장하는거라 생각할 수 있다.
그러나 사실 저렇게 호출을 하는 순간 또 다시 height를 위한 set()가 호출이 되어버려서 다시 if문을 확인하고, 거기서 또 통과하고, 다시 height = value를 시도하고, 거기서 height를 위한 set()가 호출이 되고... 이렇게 무한루프가 진행되어 버린다.
그래서 '실제로' 값이 저장되는 또다른 영역, backing field가 등장하는 것이다.

var height = height_param
	set(value) {
    	if (value > 0) field = value
    }

이러면 무한루프가 진행되지 않을 것이기 때문.

class의 property는 initialization해야 한다.

또 class의 모든 property들은 (lateinit같은 것을 쓰지 않는다면) 무조건 initialization을 해야 한다. 예를들어 다음 코드는 컴파일을 하지만

class human(height_param: Int) {
	val height: Int = height_param
}

이건 컴파일을 거부한다.

class human(height_param: Int) {
	val height: Int
}

...해야 한다?

다만 앞의 lateinit 말고 예외가 하나 더 있는데 backing field가 없는 경우에는 initialize를 할 필요 없다.
아니, 정확히 말하면 backing field가 없는 경우에는 initialize를 하면 안된다. 하려 하면 컴파일러가 거부한다.
이에 대해 더 얘기하기전에 backing field가 없는 경우가 언젠지를 먼저 알아보자. 이는 사실 backing field가 있는 경우의 정확히 반대의 경우다. 그 경우가 언제냐면

  • default getter이나 default setter을 사용하는 경우
  • custom setter을 사용하는데 거기서 field가 사용되는 경우

custom getter및 setter을 사용하면서, 둘 다 field가 사용되지 않으면 backing field가 만들어지지 않는다. 여기에 해당하는 경우는

  • val로 선언된 attribute이며, custom getter을 사용하고 거기서 field가 안 등장하는 경우
  • var로 선언된 attribute이며, custom getter과 custom setter을 사용하고, 둘 다 field가 등장하지 않는 경우.

먼저 왜 field가 존재하는 경우의 규칙이 저런지는 직관적이다. 아까 default getter / setter의 코드를 보면 field가 사용되고 있고, custom setter서 field를 사용하면 당연히 field가 존재해야 하기 때문이다.
그러면 field가 존재하지 않는 경우의 규칙이 저런 이유도 직관적이다. 말 그대로 field가 등장할 이유가 없어서 존재하지 않는 것이다.

그러면 왜 field가 없으면 initialization을 거부할까. 이 부분이 내가 의문을 가진 부분이다.

initialization을 '강요' / initialization을 '금지'

사실 이거랑 비슷하면서 다른 의문을 가진 분이 한분 있다. 바로 field가 있으면 initialization을 컴파일러가 강요하는가에 대해서다.
거기서 나온 답변들 중 하나를 인용하자면

내 생각이 틀릴 수는 있는데, Java에서 field가 initialize되지 않는 경우 버그가 생기는 경우가 있어서 이를 간단하게 방지하기 위해 field가 있으면 initialize를 강요하도록 compiler을 만든 것 같다.

그래서 어쩌면 field가 없을 때 initialization을 금지한 것도 이거랑 관련이 있지 않을까... 싶다. 그리고 굳이 이러지 않을 만한 이유도 딱히 찾지를 못하겠고 documentation에서도 자세히 나온건 없어가지고 그냥 그런가보다 하고 넘기기로 했다.
누군가 더 자세히 알고 있다면 댓글로 남겨주시면 감사하겠습니다.

profile
CS 학부생, 핵심 관심 분야 : Embed/System/Architecture/SWE

0개의 댓글