Coding Log


Kotlin

본 카테고리는 2017년 Android 공식 언어로 채택된 Kotlin에 관하여 다룬다.

Kotlin을 이용해 개발하는 Android는 추후 따로 다루기로 하고 언어 자체에만 집중한다.

참고 kotlin 공식 사이트

Class - Property와 Field

속성값 선언(Declaring Properties)

Kotlin의 클래스는 속성값(이하 프로퍼티)를 가질 수 있다.

변수와 상수의 선언과 마찬가지로 varval를 사용할 수 있다.

class Address {
    var name: String = ...
    var street: String = ...
    var city: String = ...
    var state: String? = ...
    var zip: String = ...
}

프로퍼티를 사용할 땐 Java에서와 마찬가지로 프로퍼티명으로 참조할 수 있다.

fun copyAddress(address: Address): Address {
    val result = Address() // there's no 'new' keyword in Kotlin
    result.name = address.name // accessors are called
    result.street = address.street
    // ...
    return result
}

Getter / Setter

프로퍼티 선언의 전체 구문은 아래와 같다.

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

프로퍼티의 선언시 위에 보이는 property_initializergetter setter는 선택사항이며, 프로퍼티의 타입은 initializer, getter, setter의 반환 타입에서 추론할 수 있다면 생략이 가능하다.

아래의 예제 코드를 통해 이해하자 보자.

var allByDefault: Int? // error: explicit initializer required, default getter and setter implied
var initialized = 1 // has type Int, default getter and setter

val로 선언하여 읽기 전용 프로퍼티를 선언하는 전체 구문은 아래와 같다.

val simple: Int? // has type Int, default getter, must be initialized in constructor
val inferredType = 1 // has type Int and a default getter

위의 예제 코드를 보면 두 가지 차이점을 확인할 수 있는데, 당연히 var가 아닌 val로 선언하며, 값이 고정되기 때문에 setter가 허용되지 않는다.

Kotlin에서 사용할 수 있는 일반적인 함수들처럼 프로퍼티 선언시 getter/setter를 커스텀할 수 있다.

getter는 아래와 같이 커스텀하고

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

setter는 아래와 같이 커스텀한다.

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value) // parses the string and assigns values to other properties
    }

관례적으로 커스텀 setter에서 파라미터로 value라는 값을 쓰지만, 이는 개발자 마음대로 변경할 수 있다.

상술했듯 Kotlin 1.1부터는 getter에서 타입 추론이 가능하면 프로퍼티의 타입 생략이 가능하다.

val isEmpty get() = this.size == 0  // has type Boolean

프로퍼티의 기본적인 구현을 바꾸지않고 어노테이션을 추가하거나 접근자를 임의로 바꾸고 싶다면 아래와 같이 구현부 () 없이 정의한다.

var setterVisibility: String = "abc"
    private set // the setter is private and has the default implementation

var setterWithAnnotation: Any? = null
    @Inject set // annotate the setter with Inject

지원 필드(Backing Fields)

Kotlin의 클래스는 당연히 필드를 가질 수 있다.

하지만 접근제어자를 커스텀하는 경우 지원 필드가 필요한 경우가 있는데, 이를 제공하기 위해 field라는 식별자를 제공한다.

var counter = 0 // the initializer value is written directly to the backing field
    set(value) {
        if (value >= 0) field = value
    }

field 식별자는 오로지 프로퍼티의 접근자에서만 사용할 수 있다.

Kotlin에서는 접근자의 기본 구현을 하나 이상 사용하거나, 커스텀하여 field 식별자로 지원 필드에 접근하는 경우

해당 프로퍼티를 위한 지원 필드를 생성해준다.

예를 들어 아래의 코드는 지원 필드가 존재하지 않는다.

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

지원 프로퍼티(Backing Properties)

위에서 다룬 자동으로 생성해주는 지원 필드의 방식이 구현하고자하는 프로그램과 맞지않을 때는 지원 프로퍼티로 대체할 수 있다.

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

위의 코드를 보면 느낄 수 있겠지만 모든 면에서 Java와 유사하다.

이는 기본 getter와 setter를 가진 private 프로퍼티에 접근하는 경우 함수 호출 오버해드가 발생하지 않도록 최적화하기 때문이다.

컴파일 타임 상수(Compile-Time Constants)

컴파일 시점에 알아야 하는 프로퍼티값은 const를 이용하여 컴파일 타임 상수로 설정할 수 있다.

const를 사용하기 위한 프로퍼티 값은 아래와 조건들을 충족해야 한다.

  • Top-level or member of an object
    • 최상위 혹은 object의 멤버 프로퍼티
  • Initialized with a value of type String or a primitive type
    • String이나 primitive type으로 초기화
  • No custom getter
    • getter 커스텀이 없음

아래와 같은 프로퍼티를 어노테이션에서 사용할 수 있다.

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"

@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }

지연 초기화 프로퍼티와 변수(Late-Initialized Properties and Variables)

일반적으로 non-null 타입으로 선언한 프로퍼티는 생성자에서 초기화해준다.

하지만 종종 불편함을 야기시키는데, 의존성 주입이나 단위 테스트의 메소드에서 프로퍼티를 초기화하는 경우에 발생한다.

위의 경우엔 생성자에 non-null 타입의 초기화 값을 제공할 수 없는데, 클래스의 구현부에서는 참조하려고 할 것이기 때문이다.

이런 경우엔 프로퍼티에 lateinit을 붙여서 처리한다.

public class MyTest {
    lateinit var subject: TestSubject

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

    @Test fun test() {
        subject.method()  // dereference directly
    }
}

lateinit을 사용하려면 조건이 있다.

기본 생성자에 포함되지 않고 프로퍼티의 커스텀된 getter/setter를 가지지 않는 클래스에 선언한 var 프로퍼티에 사용할 수 있다는 것이다.

단, Kotlin 1.2부터는 최상위 프로퍼티와 지역 변수에도 사용할 수 있다.

추가적으로 해당 프로퍼티의 타입은 non-null 타입이어야 하며 primitive 타입이면 안된다.

lateinit은 초기화전에 접근하는 경우엔 예외를 발생시키며, 해당 예외는 접근할 프로퍼티와 초기화 여부를 식별해준다.

lateinit var의 초기화 검증 Checking whether a lateinit var is initialized (since 1.2)

lateinit var를 이미 초기화했는지 검증하려면 프로퍼티를 참조할 isInitialized를 사용하면 된다.

아래 예제 코드를 보자.

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

위와 같은 검증은 접근 가능한 프로퍼티에 대해서만 가능하다.

예를 들어, 같은 타입 혹은 외부 타입에 선언되었거나 같은 파일의 최상위에 선언된 프로퍼티가 해당된다.

DISQUS 로드 중…
댓글 로드 중…

트랙백을 확인할 수 있습니다

URL을 배껴둬서 트랙백을 보낼 수 있습니다