[Kotlin] 프로퍼티, 커스텀 접근자, 지연 초기화
프로퍼티 = 필드 + 접근자
- 클래스 내부의 변수 선언은 자바에서는 필드 선언을 의미하지만 코틀린에서는 프로퍼티 선언을 의미한다.
- 즉, 필드 뿐만 아니라 접근자 메서드도 알아서 생성해준다.
```kt
class Person(
val name: String, // val은 읽기 전용 프로퍼티. private 필드와 getter.
var isMarried: Boolean // private 필드와 getter, setter.
)
/* 코틀린에서 호출할 때 (자동으로 getter, setter 호출로 변환해준다) */
person.name
person.isMarried = true
/* 자바에서 호출할 때 */
person.getName()
person.isMarried() // boolean 프로퍼티 getter는 is prefix다
person.setMarried(true)
```
* 반대로 자바로 작성한 클래스도 프로퍼티 명명 형식을 맞춰 준다면 코틀린 클래스 프로퍼티처럼 사용할 수 있다.
커스텀 접근자
단순히 값을 반환하는 접근자가 아니라 특정 연산을 수행한 결과를 반환해야 하는 경우라면, 접근자를 직접 작성할 수 있다.
```kt
val isSquare: Boolean
get() = height == width
var counter: Int = 0
private set
```
isSquare 예제 처럼 호출될 때 마다 계산해서 수행 결과를 반환하는 방법이 있고,초기화시, 그리고 종속 변수 값이 변경될 때 마다 변수 값을 다시 계산해두고 호출될 때는 단순히 계산 결과를 반환하기만 하도록 구성하는 방법이 있는데전자는 호출 오버헤드가, 후자는 메모리 오버헤드가 발생한다. 특별히 많이 호출되지 않는다면 전자로 짜는게 더 보기 좋은 듯.Note ) ``kt lateinit``은 커스텀 접근자를 사용할 수 없으니 주의!
setter 내에서 변수 값 접근은 field 키워드로
```kt
class User (val name: String){
var address: String = "unspecified"
set(value: String) {
println("Address was changed for $name: $field -> $value")
field = value
}
}
```
* 커스텀 접근자를 지정할 때 내부에서 ``kt field`` 키워드를 사용하지 않는다면 뒷받침 필드가 필요 없다는 의미 이므로 알아서 필드가 생성되지 않는다 (매 호출 마다 eval 된다)
data class에서 custom getter 사용하고 싶을 때
backing property를 사용한다.
```kt
data class Test(private val _value: Int) {
val value
get() = value
}
```
지연 초기화
- 안드로이드에서 액티비티 초기화에 사용하는 ``kt onCreate``같이 생성자 안에서 초기화하지 않고 특별한 메소드에서 초기화해야 하는 경우 등, 지연 초기화 하고 싶은 경우 일단 변수에 ``kt null``을 집어넣고 이후에 초기화해야 하는 방식이 떠오른다.
- 그러나 변수에 일단 ``kt null``을 집어넣으려면 타입을 ``kt Type?``로 선언해주어야 하는데, 이러면 이 변수를 가져다 쓰는 모든 구간에서 널체크를 해줘야 하므로, 굉장히 비효율적이다.
- 지연 초기화 하면서도, 타입을 non-nullable로 사용하는 방법은?
backing property를 사용한 lateinit
```kt
class Person {
private var _emails: List<Email>? = null
val emails: List<Email>
get() {
if (_emails == null) {
_emails = loadEmails(this)
}
return _emails!! // var 이라 스마트 캐스팅이 작동하지 않으므로 !!
}
}
```
- 가능한 방법이지만, 지저분하고, thread safe 하지도 않다.
val 지연 초기화 : by lazy
```kt
class Person {
val emails by lazy { loadEmails(this) }
```
- 부대 코드가 없어 깔끔하게 끝난다.
- lazy 함수는 기본적으로 thread safe하다.
- 하지만 필요에 따라 동기화에 사용할 락을 lazy에 전달 할 수도 있고, 반대로 lazy 함수가 동기화를 하지 못하게 막을 수도 있다.
- by를 이용해서 프로퍼티 위임한 것인데, 상세 내용은 [Kotlin] delegate 키워드 : by
var 지연 초기화 : lateinit
```kt
class LateInitTest {
lateinit var myService: MyService
@Before fun setUp() {
myService = MyService()
}
}
// 초기화 되었는지 체크는
::myService.isInitialized
```
- 초기화하기 전에 프로퍼티에 접근하면 예외가 발생하며, 단순 NPE가 발생하는 것 보다 확인하기 용이하다.
- Note ) ``kt lateinit`` modifier is not allowed on properties of primitive types
- 이런 경우 primitive type의 기본값으로 초기화 하면 된다. null 표현이 필요한 경우 그냥 nullable 타입으로 만들어주면 된다. (이러면 wrapper 타입이 되므로)
- [Kotlin]에서는 원시 타입과 참조 타입을 별도 타입으로 구분하지 않는다.
lateinit VS Delegates.notNull()
codechacha.com/ko/diff-between-deligate-and-lateinit-in-kotlin/
lateinit은 Primitive type에는 사용할 수 없다.
- 지연 초기화가 필요한 경우 getter에서 수행하는 것을 고려
- 하지만 지연 초기화가 더 비효율적인 경우가 더러 있기 때문에 꼭 필요한지 생각해볼 것.
'Java Stack > Kotlin' 카테고리의 다른 글
[Kotlin] delegate 키워드 : by (0) | 2017.12.02 |
---|---|
[Kotlin/Java] data class (0) | 2017.12.02 |
[Kotlin] Class, 생성자 (0) | 2017.12.01 |
[Kotlin] enum / when / sealed (0) | 2017.12.01 |
[Kotlin/Java] Inner Class / Nested Class (0) | 2017.12.01 |