Kotlin Backing Field

코틀린에서 상수 선언하는법

코틀린에서 상수를 선언하려고 할 때 자바를 쓰던 사람들이 가장 많이 하는 실수는 아래와 같다.

class Test {
  const val TEST_CONST = "TEST"
}

자바를 써오던 사람들은 의아해한다.

엥 이게 왜 상수 선언이 안될까? 라고.

동시에 인텔리제이에서는 다음과 같은 에러를 표시한다.

스크린샷 2020-07-03 오전 12 28 04

직역하면 Const val은 오직 오브젝트 최상단에만 허락되어진다.

즉,

const val TEST_CONST = "TEST"

class Test {

}

코틀린에선 상수를 표현할 때 위와 같이 표현되어야한다.

왜일까?

우선 Kotlin에서 const 키워드는 코틀린 클래스에서 프로퍼티로 인식하지 않는다.

프로퍼티는 기본적으로 getter, setter가 존재하는데 const의 경우 불변값이므로 setter가 존재할 수 없다.

그럼 getter는 존재할 수 있지않느냐! 라고 반박할 수 있다.

여기서 오늘의 주제인 Backing Field가 등장한다.

Kotlin Backing Field

Backing Field란 무엇일까? 직역하자면 백업 필드정도 되겠다.

우선 다음과 같은 예시를 보자.

class Test {
    val str = "Hello"
        get() = str + "World"
}

위 예시에서 우리는 일반적으로 메인 코드에서 다음과 같이 호출했을 때 Hello World가 출력되는 것을 기대할 수 있다.

fun main() {
    val test = Test()
    println(test.str)
}

그러나 실제 출력 결과는 에러를 발생시키다가 Stack Overflow를 발생시키며 !하고 프로그램이 터진다.

자 그럼 문제가 무엇이길래 터지는 걸까?

get()의 경우 str을 호출했을 때 호출되는 코틀린의 프로퍼티 getter 메소드이다. (setter도 같은 방법으로 선언 가능)

그렇기 때문에 test.str은 사실 test.str.get()이 생략되었다고 볼 수 있다.

일반적으로 val은 참조 불변의 성질을 가진 데이터 타입이기 때문에 상수와 쉽게 혼동하게 된다.

그러나 val은 참조 불변의 성질을 갖고 있지만 자체가 바뀌지 않는 것을 의미하지는 않는다.

그 반례가 바로 위와 같이 getter()에 문자열을 더하는 경우이다.

그렇기 때문에 val를 상수로 보기 보다는 읽기 전용 데이터 타입이라고 보는게 맞겠다.

어쨌든, 다시 처음 문제로 돌아와서 test.str이 get() 메소드를 호출하기 때문에, getter 내부에서 str을 호출하는 것 또한 재귀적으로 자기 자신의 get() 메소드를 호출하게 된다.

그렇게 무한히 함수를 호출하다 스택이 펑 터지게 되는 것이다.

이 때 사용할 수 있는 것이 바로 Backing Field이다.

getter 또는 setter에서 자기 자신을 호출하는 경우

field라는 특별한 이름의 필드를 사용할 수 있다.

class Test {
    val test = "Hello"
        get() = field + "World"
}

위 메소드는 HelloWorld를 출력하게 될 것이다.

이것이 BackingField이다.

처음으로 돌아와서

class Test {
  const val TEST_CONST = "TEST"
}

우리는 처음 왜 클래스 안에서는 상수 선언이 안될까에 대해서 의문을 가졌었다.

그 이유는 간단하다, val 프로퍼티는 const 상수 키워드를 붙일 경우 그 프로퍼티가 불변하다는 것을 보장해줄 수 없다.

왜냐하면 위와 같이 getter에 BackingField를 사용하면 얼마든지 반환 값을 바꿀 수 있기 때문이다.

그렇기 때문에 상수라는 본연의 의미를 벗어나게 되고 이를 인지한 코틀린에서 const는 아예 클래스 내부의 프로퍼티가 아닌 최상위 전역 변수로 만들라는 에러를 내보내는 것이다.

마치며

val == 상수 == 참조 불변

그동안 코틀린에서 val의 의미를 위와 같이 인지하고 있었는데 단단히 잘못알고 있었다는 사실을 알게 되었다.

왜 const를 클래스 내부에서 사용할 수 없는지, 왜 전역으로 빼야하는지, Backing Field가 뭔지를 이해하고 나니 내가 얼마나 어리석은 착각을 하였는지 알 수 있게 되었다.

이 모든 과정을 설명해주시느라 바쁜 시간을 내주신 bernard에게 진심으로 감사드립니다.



© 2022. by minkuk

Powered by minkuk