Kotlin

Koltin Properties

voider 2021. 8. 1. 21:46

프로퍼티 선언 Declaring properties

Kotlin 클래스에서 프로퍼티를 만드는 키워드는 두 가지다.

  • var : mutable
  • val : immutable

Getter and Setter

프로퍼티 선언 문법은 다음과 같다. 코틀린은 getter/setter 기본 구현을 제공한다.

var <propertyName> [: <propertyType>] [= <property_initializer]
    [<getter>]
    [<setter>]

Kotlin은 이니셜라이저 또는 게터를 통해 추론 가능한 타입이라면 프로퍼티 타입을 생략할 수 있다.

프로퍼티의 커스텀 접근자를 정의할 수 있다. 만약 커스텀 getter를 정의했다면 해당 프로퍼티를 콜할 때마다 커스텀 게터의 값을 반환한다.

val isEmpty : Boolean
    get() = this.size == 0

만약 프로퍼티의 커스텀 세터를 정의했다면, 언제든 프로퍼티에 값을 할당할 수 있다.

var stringRepresentation: String
    get() = this.toString();
    set(value) {
        setDataFromString(value) // 
    }

원한다면 세터의 파라미터 네임을 지정할 수 있지만, 관례적으로 세터 파라미터의 이름은 value 다.

위에서도 말했지만 getter로 타입을 추론할 수 있다면 프로퍼티 타입을 생략할 수 있다.

val isEmpty() get() = this.size == 0;

접근자에 주석을 달거나 가시성을 변경해야 하지만, 기본 구현은 변경할 필요가 없는 경우 본문을 정의하지 않고 접근자를 정의할 수 있다.

val setterVisibility: String = "abc"
    private set // 세터는 private하고 기본 구현을 가진다.
val setterWithAnnotion: Any? = null
    @Inject set // 세터에 애노테이션 달기

Backing field

Backing Field는 getter/setter에 기본적으로 생성되는 프로퍼티다. Kotlin에서 Class 안의 프로퍼티에 값을 할당할 때 내부적으로 setter가, 값을 불러올 때는 getter가 호출된다.

counter 라는 프로퍼티가 있고, 이에 대한 getter/setter 기본 구현은 아래와 같다.

var counter = 0
    get() = field
    set(value) {
        field = value
        // counter = value // StackOverFlow. 실제 이름을 사용하면 setter를 재귀호출
    }

게터/세터 모두 counter 가 아닌 field 를 사용한다. 만약 여기서 field 키워드 대신 counter를 사용하면 StackOverFlow를 호출한다. 코틀린은 기본적으로 프로퍼티에 대한 게터, 세터를 가지고 있기 때문에

counter = value 이런 식으로 값을 할당하면 counter의 세터를 재귀적으로 호출하는 셈이다. 이 문제를 해결하기 위해 코틀린에서 field 라는 키워드로 Backing field를 제공하는 것이다.

접근자 중 하나 이상 기본 구현을 사용하거나, 커스텀 접근자가 필드 식별자를 통해 이를 참조하는 경우, 프로퍼티에 대한 backing field가 생성된다.

Complie-time constants

const modifier가 붙은 변수는 컴파일 타임에 이 변수가 read only속성임을 보장한다. const가 붙은 프로퍼티는 제약 조건이 있다.

  • 반드시 top-level에 선언되거나 object 또는 companion object 의 멤버여야 한다.
  • 값이 String 또는 primary type이어야 한다.
  • custom getter를 가질 수 없다.

Late-initialized 프로퍼티와 변수

일반적으로 non-null 프로퍼티를 선언할 때는 생성자에서 초기화한다. 그러나 그렇지 않은 경우도 있다. 예를 들어 프로퍼티가 DI를 통해 생성되거나, unit test의 setup 메서드에서 생성해야 할 때가 있다. 이런 케이스에서는 non-null 프로퍼티라면, 생성하는 게 불가능하다. 이럴 때 lateinit modifier를 사용해서 초기화를 뒤로 미룰 수 있다.

class MyTest {
    lateinit var subject: TestSubject

    @Setup fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject.method()
    }
}

lateinit 를 사용하려면

  1. top-level 프로퍼티이면서
  2. var 변수여야 한다.

lateinit 프로퍼티를 초기화되지 않은 상태에서 사용하면 초기화되지 않았다는 사실을 명확하게 식별 가능한 예외가 발생한다.

또한 lateinit var 프로퍼티가 이미 초기화되었는지 확인하려면 .isInitialized 를 사용하면 된다.

if (foo::bar.isInitialized)
    println(foo.bar)