Kotlin에서는 val과 var로 변수를 선언할 수 있다.
val는 변경 불가능한 참조를 저장하는 변수이고,
어원은 value로, 자바에서 final 변수에 해당한다.
var는 변경 가능한 참조를 저장하는 변수이고,
어원은 variable로, 자바에서 일반 변수에 해당한다.
val birthday: String = "2026.01.09"
var age: Int = 0
즉, var인 일반적인 변수로 언제든 값을 수정할 수 있는 변수이며, val은 읽기 전용으로 한번 초기화하면 값이 변경되지 않는 변수인 것이다.
아 참고로, val 변수가 불변일지라도 참조에 대한 불변으로 참조를 가리치는 객체의 내부 값은 변경될 수 있다.
즉, Array 같은 목록형 변수로 초기화된다면 처음 초기화 때의 목록과 다른 내용으로 변경되어도 해당 목록에 대한 참조의 값은 변경되지 않기 때문에 val 변수는 변경된 목록을 가리키게 되는 것이다.
오늘은 이 val 변수에서 종종 사용하게 되는 by lazy에 대해 알아보고자 한다.
by lazy
우리는 변경되지 않는 val 변수 값이 필요하지만 지금 당장 값을 초기화하지 않고
값을 사용하는 처음 시점에 초기화가 필요할 때 종종 by lazy를 사용하곤 한다.
val birthday by lazy { loadBirth(this) }
그럼 by lazy는 어떤 방식으로 값을 초기화 시키는 것이고 어떤 식으로 동작하는 걸까.
우선 by lazy는 by + lazy로, 위임 키워드 by와 지연 초기화를 위한 lazy 함수의 조합이다.
위임 (by)
객체가 직접 작업을 수행하지 않고 다른 도우미 객체가 그 작업을 처리하게 맡기는 디자인 패턴
Kotlin의 프로퍼티의 다음과 같은 예시를 통해 위임에 대해 알아보도록 하자.
* 프로퍼티: Kotlin에서 var, val로 선언된 변수는 모두 접근 로직이 캡슐화된 프로퍼티이기 때문에 이하 프로퍼티로 명칭
다음과 같은 exPreoperty라는 위임 프로퍼티가 있다고 하자,
class Example {
var exProperty: Type by Delegate()
}
참고 출처: Kotlin In Action 도서
이 코드는 컴파일러가 다음과 같이 해석한다.
delegate는 임의로 명명한 명칭이며 실제와 다르다.
class Example {
private val delegate = Delegate()
var exProperty: Type
set(value: Type) = delegate.setValue(..., value)
get() = delegate.getValue(...)
}
위의 by Delegate()를 실행할 때, 컴파일러는 숨겨진 도우미 프로퍼티(delegate)를 만들고
해당 프로퍼티를 위임 객체(Delegate)의 인스턴스로 초기화 한다.
delgate가 컴파일러가 생성한 도우미 프로퍼티이며, Delegate()가 위임 객체의 인스턴스인 것이다.
조금 복잡하게 by 키워드가 동작하지만 해당 부분은 추후 좀 더 정리해 보도록 하자.
즉, 초기화 로직을 다른 객체로 위임하여 해당 프로퍼티의 getter와 setter를 제공해 주는 키워드가 by 인 것이다.
지연 초기화 표준 라이브러리 함수 (Lazy)
Lazy는 표준 라이브러리 함수로 지연 초기화 프로퍼티를 쉽게 구현할 수 있도록 해준다.
그렇다면 지연 초기화란 어떤 것인지 코드로 풀어 작성하면 다음과 같다.
class Person(val name: String) {
private var _birthday: String? = null // 위임 객체
val birthday: String
get() {
if (_birthday == null) {
_birthday = loadBrithDay(this) // 최초 접근 시 탄생일을 가져온다.
}
return _birthday // 기존 데이터가 있으면 그 데이터를 반환
}
}
해당 기법을 뒷받침하는 프로퍼티(backging property)라는 기법이라고 한다.
즉, 위의 코드에서 birthday 프로퍼티는 실제로 birthday 값을 사용 전까지는 초기화시키기 않고 처음 사용(호출)할 때 단 한번 초기화하며 이것을 "지연" 초기화라고 한다.
다만, 위의 코드는 멀티 스레드 환경 등에서 스레드 안전하지 않기 때문에 값이 오직 값이 한 번만 초기화됨을 보장하지 못할 것이다.
하지만 위임 프로퍼티(즉, by)를 사용하면
위의 로직과 동일한 로직이 값이 오직 한 번만 초기화됨을 보장하는 getter 로직을 함께 캡슐화해 준다.
그리고 위의 예제와 같이 지연 초기화의 위임 객체를 반환해 주는 표준 라이브러리 함수가 Lazy이다.
그렇기 때문에 우리가 흔히 사용하는 by Lazy는 lazy를 통해 뒷받침하는 프로퍼티(backging property)의 위임 객체를 Lazy에게 전달받아 해당 객체의 함수를 통해 by 키워드로 위임하여 초기화하는 기법인 것이다.
val birthday by lazy { loadBirth(this) }
못다 한 이야기
- android 생애주기에서 var, val 메모리 수명
- by lazy Context 메모리 누수
- var 지연 초기화 lateinit
참고 자료
Kotlin In ACTION - 드미트리 제메로프∙스베트라나 이사코바 지음(오현석 옮김)
'Android > 학습' 카테고리의 다른 글
| [Android] targetSdk와 compileSdk의 차이, 그에 따른 영향. (0) | 2026.02.22 |
|---|---|
| [CS] "부동소수점"이란? 컴퓨터에서 소수를 표현하는 방법과 그에 따른 오차 (0) | 2026.02.07 |
| [Firebase] Remote Config, 어떻게 사용하고 왜 사용하는가? (0) | 2025.11.29 |
| [Android] Kotlin Flow의 first (with. Cold Steam, Hot Steam) (0) | 2025.11.09 |
| [CS] DataBase INDEX (0) | 2025.10.24 |