코틀린은 지연 초기화를 사용하는데 이는 클래스의 코드에 Nullable(?) 처리가 남용되는 것을 방지해준다.
지연 초기화에 사용하는 lateinit 과 lazy 를 살펴보자.
클래스 안에서 변수(프로퍼티)만 Nullable로 미리 선언하고 초기화(생성자 호출)을 나중에 해야 할 경우가 있다.
lateinit 키워드는 이럴 때 사용한다.
class Person {
var name: String? = null
init{
name = "Lionel"
}
fun process() {
name?.plus(" Messi")
print("이름의 길이 = ${name?.length}")
print("이름의 첫 글자 = ${name?.substring(0,1)}")
}
}
이 방식은 변수에 입력된 값의 메서드나 프로퍼티를 사용할 때 Safe Call(?.)이 남용되어 가독성을 떨어뜨린다.
lateinit을 사용하여 Safe Call을 사용하지 않고 코딩해보자.
class Person {
lateinit var name: String
init {
name = "Lionel"
}
fun process() {
name.plus(" Messi")
print("이름의 길이 = ${name.length}")
print("이름의 첫 글자 = ${name.substring(0,1)}")
}
}
lateinit의 특징
- var로 선언된 클래스의 프로퍼티에만 사용할 수 있다.
- null은 허용되지 않는다.
- 기본 자료형 Int, Long, Double, Float 등은 사용할 수 없다.
주의할 점이 있다.
lateinit은 변수를 미리 선언만 해 놓은 방식이기 때문에 초기화되지 않은 상태에서 메서드나 프로퍼티를 참조하면 null 예외가 발생해서 앱이 종료된다.
변수가 초기화되지 않은 상황이 발생할 수 있다면, Nullable이나 빈 값으로 초기화하는 것이 좋다.
lazy는 읽기전용 변수인 val을 사용하는 지연 초기화다.
lateinit이 입력된 값을 변경할 수 있는 반면, lazy는 입력값을 변경할 수 없다.
사용법도 조금 다르다.
class Company {
val person: Person by lazy { Person() }
init {
// lazy는 선언 시에 초기화를 하기 때문에 초기화 과정이 필요없다.
}
fun process() {
print("person의 이름은 ${person.name}") // 최초 호출하는 시점에 초기화된다.
}
}
by lazy와 변수 타입의 생략
by lazy를 사용하면 반환되는 값의 타입을 추론할 수 있기 때문에 앞의 코드에서 작성한 person 변수의 타입은 생략할 수 있다.
lazy의 특징
- 주석에 써 있듯이 선언시에 초기화 코드를 함께 작성하기 때문에, 따로 초기화할 필요가 없다.
- lazy로 선언된 변수가 최초 호출되는 시점에 by lazy{} 안에 넣은 값으로 초기화된다. 앞의 코드에서 Company 클래스가 초기화되더라도 person에 바로 Person()으로 초기화되지 않고, process 메서드에서 person.name이 호출되는 순간 초기화된다.
lazy는 주의해서 사용해야 한다.
지연 초기화는 말 그대로 최초 호출되는 시점에 초기화 작업이 일어나기 때문에 초기화하는 데 사용하는 리소스가 너무 크면(메모리를 많이 쓰거나 코드가 복잡한 경우) 전체 처리 속도에 나쁜 영향을 미칠 수 있다.
예를 들어 앞의 Company 클래스에서 처리하는 Person 클래스의 코드가 복잡하면 단순히 person.name을 호출하는 데 수 초가 걸릴 수도 있다.
따라서 복잡한 코드를 가지고 있는 클래스라면 미리 초기화해 놓고 사용하는 것이 좋다.
class Market {
lateinit var candy : Candy
init{
Log.d("Candy", "사탕의 이름은 ${candy.name} 입니다.")
}
}
다음 코드를 실행하면 오류가 발생하는데 그 이유가 뭘까?
lateinit을 사용하면 변수만 미리 선언해 놓은 방식이라 초기화 되지 않은 상태에서 메서드나 프로퍼티를 참조하면 null 예외가 발생한다.
즉 init 블럭 내에서 candy 변수를 초기화해주지 않았는데 candy.name 으로 프로퍼티를 참조했기 때문에 null 예외가 발생해서 오류가 난다.