Declaring properties
코틀린 클래스에서 속성은 var 키워드를 사용하여 변경 가능으로 선언하거나 val 키워드를 사용하여 읽기 전용으로 선언할 수 있다.
class Address {
var name: String = "Holmes, Sherlock"
var street: String = "Baker"
var city: String = "London"
var state: String? = null
var zip: String = "123456"
}
속성을 사용하려면 해당 이름으로 속성을 참조하면 된다.
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
}
Getters and setters
속성을 선언하는 완전한 구문은 다음과 같다.
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
initializer, getter, and setter는 선택 사항이다. 속성 유형은 아래와 같이 초기화 또는 getter의 반환 유형에서 유추할 수 있는 경우 선택사항이다.
var initialized = 1 // has type Int, default getter and setter
// var allByDefault // ERROR: explicit initializer required, default getter and setter implied
속성을 읽기 전용으로 선언하는 경우, var 키워드 대신 val 키워드를 사용하여 setter를 허용하지 않는다.
val simple: Int? // has type Int, default getter, must be initialized in constructor
val inferredType = 1 // has type Int and a default getter
속성에 대한 사용자 지정 접근자를 정의할 수 있습니다. 사용자 지정 getter를 정의하면 속성에 액세스할 때마다 호출된다.
val area: Int // property type is optional since it can be inferred from the getter's return type
get() = this.width * this.height
사용자 정의 getter에 의해 타입 추론이 가능한 경우 속성 타입은 생략이 가능하다.
val area get() = this.width * this.height
사용자 지정 setter를 정의하면 초기화를 제외하고 속성에 값을 할당할 때마다 호출된다. 관례상 setter의 매개변수 이름은 value 이지만 원하는 경우 변경이 가능하다.
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // parses the string and assigns values to other properties
}
만약 접근자(getter, setter)에 애노테이션을 달거나 가시성을 변경해야 하지만 기본 구현을 변경할 필요가 없는 경우 본문을 정의하지 않고 접근자를 정의할 수 있다.
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
(= 지원 필드)
코틀린에서 속성의 일부로 메모리에 값을 유지하기 위한 필드를 제공한다. 필드는 직접 선언할 수 없다. 그러나, 속성에 지원필드가 필요한 경우 코틀린은 자동으로 제공해준다. 지원 필드는 field 키워드를 사용해서 속성 접근자에서만 참조할 수 있다.
var counter = 0 // the initializer assigns the backing field directly
set(value) {
if (value >= 0)
field = value
// counter = value // Setter의 무한 재귀호출을 유발하기 때문에 허용 X
}
위의 코드에서 field = value를 보면, 프로퍼티 내부의 값을 직접 변경하려고 하고 있다. 이렇게 직접 프로퍼티에 접근할 때 사용한다.
속성이 접근자 중 하나 이상의 기본 구현을 사용하거나 사용자 지정 접근자가 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")
}
JVM 플랫폼에서 기본 getter와 setter를 가진 private 속성에 대한 접근은 함수 호출 오버헤드를 피하기 위해 최적화 되어 있다.
Compile-time constants
만약, 읽기 전용 속성의 값이 컴파일 시간에 알려진 경우 const키워드를 이용하여 컴파일 시간 상수로 표시할 수 있다.
- 최상위 속성이거나 Object 또는 동반객체의 멤버여야 한다.
- String 타입 또는 기본 유형의 값으로 초기화해야 한다.
- 사용자 지정 getter가 값이 될 수 없다.
이러한 속성은 애노테이션에서 사용할 수 있다.
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
Late-initialized properties and variables
일반적으로 null 이 아닌 형식으로 선언된 속성은 생성자에서 초기화 해야 한다.
하지만, 속성을 종속성 주입을 통해 초기화하거나 단위 테스트의 설정 방법에서 초기화하는 경우 생성자에서 null이 아닌 초기화 블록을 제공할 수 없지만 클래스 본문 내부에서 속성을 참조할 때 null 검사를 피하고자 한다.
이러한 경우를 처리하기 위해 lateinit 키워드를 사용하여 지연 초기화 속성으로 표시할 수 있다.
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // dereference directly
}
}
이 수정자는 클래스 본문 내부에 선언된 var 속성 (기본 생성자가 아닌, 속성에 사용자 지정 getter 또는 setter가 없는 경우에만) 과 최상위 속성 및 지역 변수에 사용할 수 있다. 속성 또는 변수의 유형은 null 이 아니어야 하며 기본 유형은 허용되지 않는다.
lateinit 속성이 초기화되기 전에 해당 속성에 접근하면 접근한 속성과 초기화되지 않았다는 사실을 명확하게 식별하는 특별한 예외가 발생한다.
checking whether a lateinit var is initialized
lateinit var이 이미 초기화 되었는지 확인하려면 해당 속성에 대한 참조에서 .isInitialized 를 사용한다.
if (foo::bar.isInitialized) {
println(foo.bar)
}
이 검사는 동일한 타입, 외부 타입 중 하나 또는 동일한 소스 파일의 최상위 수준에서 선언될 때 어휘적으로 엑세스 할 수 있는 속성에 대해서만 사용할 수 있다.
Delegated properties
(= 위임된 속성)
가장 일반적인 종류의 속성은 단순히 지원 필드에서 읽거나 쓸 수 있지만 사용자 정의 getter 및 setter를 사용하면 속성을 사용하여 속성의 모든 종류의 동작을 구현할 수 있다. 첫번째 종류의 단순함과 두번째 종류의 다양성 사이 어딘가에 속성이 수행할 수 있는 작업에 대한 공통 패턴이 존재한다. → lazy value, 맵에서 키로 읽기, 데이터베이스 액세스, 접근시 알림 등