(Kotlin) 프로퍼티, 커스텀 접근자, 지연 초기화
프로퍼티 = 필드 + 접근자
- 클래스 내부의 변수 선언은 자바에서는 필드 선언을 의미하지만 코틀린에서는 프로퍼티 선언을 의미한다.
- 즉, 필드 뿐만 아니라 접근자 메서드도 알아서 생성해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)
* 반대로 자바로 작성한 클래스도 프로퍼티 명명 형식을 맞춰 준다면 코틀린 클래스 프로퍼티처럼 사용할 수 있다.
커스텀 접근자
단순히 값을 반환하는 접근자가 아니라 특정 연산을 수행한 결과를 반환해야 하는 경우라면, 접근자를 직접 작성할 수 있다.
1
2
3
4
5
val isSquare: Boolean
get() = height == width
var counter: Int = 0
private set
isSquare 예제 처럼 호출될 때 마다 계산해서 수행 결과를 반환하는 방법이 있고,
초기화시, 그리고 종속 변수 값이 변경될 때 마다 변수 값을 다시 계산해두고 호출될 때는 단순히 계산 결과를 반환하기만 하도록 구성하는 방법이 있는데
전자는 호출 오버헤드가, 후자는 메모리 오버헤드가 발생한다. 특별히 많이 호출되지 않는다면 전자로 짜는게 더 보기 좋은 듯.
[!info]
lateinit
은 커스텀 접근자를 사용할 수 없으니 주의!
setter 내에서 변수 값 접근은 field 키워드로
1
2
3
4
5
6
7
class User (val name: String) {
var address: String = "unspecified"
set(value: String) {
println("Address was changed for $name: $field -> $value")
field = value
}
}
* 커스텀 접근자를 지정할 때 내부에서 field
키워드를 사용하지 않는다면 뒷받침 필드가 필요 없다는 의미 이므로 알아서 필드가 생성되지 않는다 (매 호출 마다 eval 된다)
data class에서 custom getter 사용하고 싶을 때
backing property를 사용한다.
1
2
3
4
data class Test(private val _value: Int) {
val value
get() = value
}
지연 초기화
- 안드로이드에서 액티비티 초기화에 사용하는
onCreate
같이 생성자 안에서 초기화하지 않고 특별한 메소드에서 초기화해야 하는 경우 등, 지연 초기화 하고 싶은 경우 일단 변수에null
을 집어넣고 이후에 초기화해야 하는 방식이 떠오른다. - 그러나 변수에 일단
null
을 집어넣으려면 타입을Type?
로 선언해주어야 하는데, 이러면 이 변수를 가져다 쓰는 모든 구간에서 널체크를 해줘야 하므로, 굉장히 비효율적이다. - 지연 초기화 하면서도, 타입을 non-nullable로 사용하는 방법은?
backing property를 사용한 lateinit
1
2
3
4
5
6
7
8
9
10
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
1
2
class Person {
val emails by lazy { loadEmails(this) }
- 부대 코드가 없어 깔끔하게 끝난다.
- lazy 함수는 기본적으로 thread safe하다.
- 하지만 필요에 따라 동기화에 사용할 락을 lazy에 전달 할 수도 있고, 반대로 lazy 함수가 동기화를 하지 못하게 막을 수도 있다.
- by를 이용해서 프로퍼티 위임한 것인데, 상세 내용은[Kotlin] delegate 키워드 : by
var 지연 초기화 : lateinit
1
2
3
4
5
6
7
8
9
10
class LateInitTest {
lateinit var myService: MyService
@Before fun setUp() {
myService = MyService()
}
}
// 초기화 되었는지 체크는
::myService.isInitialized
- 초기화하기 전에 프로퍼티에 접근하면 예외가 발생하며, 단순 NPE가 발생하는 것 보다 확인하기 용이하다.
Note )
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에서 수행하는 것을 고려
- 하지만 지연 초기화가 더 비효율적인 경우가 더러 있기 때문에 꼭 필요한지 생각해볼 것.
This post is licensed under CC BY 4.0 by the author.