Kotlin ‐ 지연과 위임 - dnwls16071/Backend_Summary GitHub Wiki
📚 lateinit과 lazy()⭐
- lateinit : 초기값을 지정하지 않고, null이 들어갈 수 없는 변수를 선언하는 키워드이다.
- lateinit 변수가 초기화되지 않은 상태에서 접근하면 UninitializedPropertyAccessException이 발생한다. 이는 컴파일 타임에 널 가능성 체크를 하지 않는 대신, 런타임에 초기화 여부를 명시적으로 확인하는 방식이기 때문이다.
- Kotlin의 Int/Long은 Java의 int/long으로 변환된다. 그런데 lateinit은 nullable 변수로 변환되어야 한다. 그렇기에 lateinit을 primitive type에 사용할 수 없다.
- Kotlin에서 Spring을 사용할 때
@Value어노테이션을 처리하는 좋은 방법은 생성자 주입을 사용하는 것이다. 이는 불변성을 유지하고 테스트를 용이하게 하며 의존성을 명확하게 보여줄 수 있다. - lateinit을
@Value와 함께 사용하는 것이 완전히 잘못된 방식은 아니지만 Kotlin과 Spring의 권장 패턴에서 생성자 주입을 통해 불변성을 확보하고 의존성을 명확히 하는 것이 좋다.- lateinit의 경우 var을 사용해 값 변경을 가능하게 해주지만 by lazy의 경우 val을 사용하기 때문에 값 변경이 불가능하다.
- lateinit의 경우 초기화 이후에 계속해 값이 바뀔 수 있을 때 사용하는 것을 추천하고 by lazy의 경우 초기화 이후에 읽기 전용 값으로 사용할 때 사용하는 것을 추천한다.
Kotlin Documentation
- Normally, you must initialize properties in the constructor. However, this isn't always convenient. For example, you might initialize properties through dependency injection or inside the setup method of a unit test.
public class OrderServiceTest {
lateinit var orderService: OrderService
@SetUp fun setup() {
orderService = OrderService()
}
@Test fun processesOrderSuccessfully() {
// Calls orderService directly without checking for null
// or initialization
orderService.processOrder()
}
}
📚 by lazy의 원리와 위임 프로퍼티⭐
- by lazy는 Kotlin의 지연 초기화 기능을 구현하는 가장 일반적인 방법이다.
- 이는 변수가 처음 사용되는 시점에 초기화가 이루어지도록 하며, 초기화에 비용이 많이 드는 객체를 효율적으로 다룰 때 유용하다.
- 지연 초기화 : lazy 블록 내 코드는 해당 변수가 처음으로 호출될 때 실행된다. 그전까지는 변수가 메모리에 할당되지 않는다.
- 단 한 번의 초기화 : 변수가 일단 초기화되고 나면, 이후에 다시 호출될 때는 이미 초기화된 값을 재사용한다. 초기화 블록은 다시 실행되지 않는다.
- 불변성 : by lazy는 읽기 전용 속성 즉,
val에만 사용할 수 있다. 따라서 초기화된 값은 변경이 불가능하다. - 쓰레드 안전성 : by lazy는 쓰레드 안전을 보장한다. 여러 스레드에서 동시에 접근해도 초기화 로직은 한 번만 실행되도록 설계되어 있다.
- 모든 타입 사용 가능 : lateinit과 달리, 원시 타입과 nullable 타입을 포함한 모든 타입에 사용할 수 있다.
📚 코틀린의 표준 위임 객체❓
Lazy Properties
by lazy { ... }문법에서 lazy는 람다를 인자로 받아 Lazy 타입의 위임 객체를 생성하고 반환한다.- lazy는 프로퍼티에 최초 접근(getter 호출)할 때만 람다 코드를 실행해 값을 계산하고, 이후 접근부터는 캐시된 값을 사용한다.
- 기본적으로 지연 속성(lazy properties)의 평가는 동기화된다.
- lazy 함수의 기본 동작 모드는 LazyThreadSafetyMode.SYNCHRONIZED로, 멀티스레드 환경에서 여러 스레드가 동시에 접근하더라도 단 한 번만 초기화가 이루어지도록 보장한다.
- LazyThreadSafetyMode.SYNCHRONIZED 모드에서는 동기화 메커니즘을 통해 하나의 스레드만 초기화 작업을 수행하게 하고, 초기화가 완료된 후에는 모든 스레드가 동일하고 올바르게 초기화된 값을 공유한다.
Observable properties
- 프로퍼티의 값이 변경될 때마다 특정 동작(콜백)을 실행하도록 하는 것이다.
📚 위임과 관련된 몇 가지 추가 기능❓
- ReadOnlyProperty는 읽기 전용(val) 프로퍼티의 위임 객체를 만들 때 사용되는 인터페이스이다.
- 포함하는 메서드: getValue() 메서드만 정의되어 있다.
- 동작 방식: 프로퍼티에 접근(get)할 때마다 getValue() 메서드가 호출되어 값을 반환한다.
- 사용 대상: val 프로퍼티에 by 키워드로 위임할 객체를 만들 때 구현한다.
- ReadWriteProperty는 읽기/쓰기(var) 프로퍼티의 위임 객체를 만들 때 사용되는 인터페이스이다.
- 포함하는 메서드: ReadOnlyProperty를 상속받으므로 getValue() 메서드와 함께 setValue() 메서드가 추가로 정의되어 있다.
- 사용 대상: var 프로퍼티에 by 키워드로 위임할 객체를 만들 때 구현한다.
- 동작 방식:
- 프로퍼티에 접근(get)할 때는 getValue() 메서드가 호출된다.
- 프로퍼티에 값을 할당(set)할 때는 setValue() 메서드가 호출된다.
📚 Iterable과 Sequence(feat. JMH)
- Iterable과 Sequence의 중요한 차이점은 평가 방식에 있으며, 이로 인해 성능과 메모리 사용량에 큰 차이가 발생한다.
| 특징 | Iterable(컬렉션) | Sequence |
|---|---|---|
| 평가 방식 | 즉시 평가(각 중간 연산이 전체 컬렉션에 적용된 후 그 결과로 새로운 컬렉션이 생성된다. | 지연 평가(최종 연산 호출되기 전까지 아무런 계산도 수행하지 않는다. 각 요소가 필요할 때마다 연산 체인이 순차적으로 적용된다. |
| 연산 순서 | 단계별 연산 | 요소별 연산 |
| 메모리 사용 | 중간 컬렉션 생성된다. | 중간 컬렉션이 생성되지 않는다. |
| 성능 측면 | 오버헤드 적다. | 오버헤드가 존재한다. |
| 적합한 상황 | 데이터가 적고 연산 자체가 간단한 경우 | 데이터가 많거나 연산 단계가 많아 중간 컬렉션 생성이 부담스러운 경우 |
- JMH는 Java Microbenchmark Harness의 약자로 JVM 기반 언어에서 작은 코드 조각의 성능을 정밀하게 측정하기 위한 벤치마킹 도구이다.
- 마이크로 벤치마킹: 개별 메서드나 코드 블록과 같이 작은 단위의 성능을 측정하는 데 특화되어 있습니다.
- 정확성: JVM의 복잡한 동작(최적화, 가비지 컬렉션 등)을 고려하여 일관성 없는 측정 결과를 피하고 신뢰성 높은 데이터를 얻을 수 있습니다.
- 다양한 측정 모드: 처리량(Throughput), 평균 실행 시간(Average Time), 샘플링 시간(Sample Time) 등 다양한 벤치마킹 모드를 지원합니다.
- 워밍업: 실제 측정 전에 JVM이 최적화될 수 있도록 코드를 미리 실행하는 워밍업 단계를 거칩니다.
- 애노테이션 기반: @Benchmark와 같은 애노테이션을 사용하여 벤치마크 코드를 쉽게 작성할 수 있습니다.
- JVM 언어 지원: 자바뿐만 아니라 JVM을 대상으로 하는 코틀린, 스칼라 등에서도 사용 가능합니다.
참고했던 내용들