ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Kotlin] Backing Fields & Backing properties
    Kotlin 2024. 1. 30. 15:34
    반응형

     

    이번 포스팅은 Backing FieldsBacking properties에 대해 정리해볼까한다.

     

    개발하면서 너무 자연스럽게 사용하고 있었는데도,

    “그래서 한문장으로 정리하면?” 라고 묻는다면 대답하기 어려울 것 같았다.

     

    Backing FieldsBacking properties의 정의, 왜 그리고 어떻게 작성하는지에 대해 정리해보자.

     

    Field와 Property

    자바에서 필드(field)는 클래스에 선언된 멤버 변수를 말한다.

     

    코틀린에서의 멤버 변수는 프로퍼티(property)라고 하는데,
    자바의 필드와는 다르게 멤버 변수와 접근자(getter, settter)를 통틀어서 지칭한다.

     

    코틀린에서 프로퍼티를 선언할 경우 접근자를 따로 정의하지 않더라도 자동 생성되고, 명시적으로 선언할 수도 있다.

    (immutable(val)로 선언할 경우 getter만 생성된다.)

    class Foo {
        val immutableNum: Int = 0 //get()만 생성됨
    	var mutableNum: Int   //get(), set() 모두 생성됨
    }

     

    Backing Fields

    코틀린에서 멤버 변수(field)는 프로퍼티의 일부로 그 값을 직접적으로 참조할 수 없다.

    프로퍼티 이름을 사용하여 호출하는 것은 getter, setter를 호출하는 것과 동일하고,

    명시적으로 선언한 접근자 내에서 프로퍼티를 호출한다면 재귀 호출이 발생한다.

    class Foo {
        var num: Int
            get() {
                return num //recursive call
            }
            set(value) {
                num = value //recursive call
            }
    }
    

     

    따라서 프로퍼티 값을 직접적으로 접근하기 위한 방법으로 Backing Field를 사용하며,

    접근자 내에서 field라는 식별자를 사용하여 멤버 변수를 참조할 수 있다.

    class Foo {
        var num: Int
            get() {
                return field
            }
            set(value) {
                if (value >= 0) field = value 
            }
    }
    

     

    +)

    Backing Field를 사용하지 않고 초기화를 할 경우에는 Initializer is not allowed here because this property has no backing field 메세지와 함께 컴파일 오류가 발생한다.

     

    Backing Properties

    Backging Properties는 프로퍼티를 외부에서 변경할 수 없도록하고
    내부에서만 변경 가능한 프로퍼티를 별도로 선언하는 것을 말한다.

    (접근자 내에서 field를 통해서만 참조 가능한 Backing field의 개념과 유사하다.)

     

    기본 구현 방법은 아래와 같다.

    가변 변수를 private로 선언하고, 불변의 public 프로퍼티의 get()에서는 private 프로퍼티를 반환하도록 정의한다.

    (공식 문서에서 backing properties에는 언더바(_)를 추가한다. )

    private var _table: Map<String, Int>? = null
    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")
        }
    

    이렇게 구현한다면 클래스 내부에서만 _table에 접근하여 데아터를 변경할 수 있고,

    클래스 외부에서는 table을 통해 읽어오기만 할 수 있다.

     

    +)

    아래와 같이 getter에 Backing Properties를 바로 할당할 수 있다.

    그러나 이렇게 구현할 경우 초기화시 데이터를 반환하며, 이후에_table가 재할당되더라도 적용되지 않기 때문에 주의해야 한다.

    private val _words = mutableListOf<String>()
    val words: List<String>
        get() = _words
    

     

    데이터 타입을 다르게 반환 가능

    Backing Properties의 큰 장점 중 하는 접근하는 데이터 타입을 다르게 반환할 수 있다는 점이다.

    예를 들면 private 프로퍼티는 MutableMap을 nullable하게 정의하고

    public 프로퍼티에서는 불변타입으로 반환할 수 있다.

    private var _table: MutableMap<String, Int>? = null
    val table: Map<String, Int>
    		get() {
    				...   
    

     

    private set

    쓰기를 외부에 노출하지 않는다면 ”setter를 private로 선언하면 되지않을까?” 하는 생각이 들 수 있다.

    물론 private set으로 정의하면 Backing Properties와 동일하게 내부에서만 변경 가능한 프로퍼티로 사용할 수 있다.

    var num: Int? = null
    	private set

    그러나 List, Map과 같이 collections 혹은 사용자 정의 클래스 타입을 사용할 경우 문제가 발생한다.

    private setter는 객체를 재할당하는 것은 막을 수 있으나,

    가변형 데이터 타입의 경우 getter를 통해 객체를 얻어 객체 내부의 데이터를 변경하는 것은 막을 수 없다.

     

     

     


     

     

    Android에서는 ViewModel 내부의 프로퍼티를 대부분 BackingProperties로 구현한다.

    뷰에서 변경할 수 없도록 분리하여 구현한다는 느낌으로 자연스럽게 사용하고 있었지만,

    정리하고보니 지금까지 너무 자바 개발자의 관점으로 코틀린을 사용하고 있었다. 

    기회가 된다면 코틀린 개념을 다시 한번 잡는 것이 좋을 것 같다. 🤔

    댓글