개발 언어/코틀린

인프런 - 코틀린 고급편 (2) 지연과 위임

jjiiiinn 2024. 7. 1. 07:31
728x90

8강. lateinit, lazy()

class Person(
    private val name: String
) {
    val isKim: Boolean
        get() = this.name.startsWith("최")

    // 홍길동 -> 홍**
    val maskingName: String
        get() = name[0] + (1 until name.length).joinToString("") { "*" }
}
fun main() {
    val person = Person("홍길동")
}
  • 클래스 인스턴스화가 이뤄지며 name에 '홍길동'이 들어감

만약, 인스턴스화 시점과 프로퍼티 초기화 시점을 분리하고 싶다면?

  • 테스트 코드 작성
    • 테스트 코드의 특징은 각 테스트 메소드가 Person을 각각 인스턴스화 하고있음
    • 두 개의 테스트 메소드의 Person 초기값이 다름
class PersonTest {
    @Test
    fun isKimTest() {
        val person = Person("김수한무")

        assertTrue(person.isKim)
    }

    @Test
    fun maskingNameTest() {
        val person = Person("홍길동")

        assertEquals(person.maskingName, "홍**")
    }
}

테스트 코드 리팩토링

  • 인스턴스화를 한번만 하고 테스트할때 변수를 초기화하기
  • 인스턴스화 할때 초기값을 넣고싶지 않음

방법1. name에 기본값 넣어주기

  • 변수가 변경 가능해야하니 val -> var로 변경
  • 기본값이 있으니 생성자에 있을 필요가 없음
class Person {
    var name: String = "홍길동" // 초기값 설정

    ...
}
class PersonTest {
    private val person = Person()
    @Test
    fun isKimTest() {
        val person = this.person.apply { name = "김수한무" }

        assertTrue(person.isKim)
    }

    @Test
    fun maskingNameTest() {
        val person = this.person.apply { name = "홍길동" }

        assertEquals(person.maskingName, "홍**")
    }
}
방법1의 문제점
  • 위와 같은 방식은 name을 초기화하지 않더라도 예외가 발생하지 않아 알수 없음

방법2. name을 nullable하게 만들기

  • 초기화하지 않았을때 에러가 발생해 알수 있고, 누군가의 이름이 null인 경우는 없음
class Person {
    var name: String? = null // nullable

    val isKim: Boolean
        get() = this.name!!.startsWith("김")

    // 홍길동 -> 홍**
    val maskingName: String
        get() = name!![0] + (1 until name!!.length).joinToString("") { "*" }
}
방법2의 문제점
  • 실제 name이 null이 되면 안되기에 계속 널처리(?., ?:, !!)가 들어가게 됨

방법3. lateinit 사용하기

  • 핵심은 인스턴스화 시점과 변수 초기화시점을 분리하는것!
  • lateinit을 사용하면 초기값을 지정하지 않고, null이 들어갈수 없는 변수를 선언 가능
class Person {
    lateinit var name: String

    val isKim: Boolean
        get() = this.name.startsWith("김")

    // 홍길동 -> 홍**
    val maskingName: String
        get() = name[0] + (1 until name.length).joinToString("") { "*" }
}

lateinit의 원리

  • lateinit변수는 컴파일 단계에서 nullable 변수로 바뀌고, 변수에 접근하려 할때 null이면 에러 발생
    • 위와같은 특징으로 lateinit은 primitive type에 사용하지 못함 (null 할당 불가)

이번에는 변수를 초기화 할때 지정된 로직은 1회만 실행하고 싶음

  • 값을 가져오는 비용이 크고 변수가 사용되지 않을수도 있을때 유용함
  • 예를 들어 아래와같이 name을 가져오는데 2초가 걸리는 변수가 존재
  • name을 호출할때 마다 2초가 걸림
  • class LazyPerson { val name: String get() { Thread.sleep(2_000) return "김수한무" } }
  • 아래와같이 init 블록에 넣으면 1회만 호출되나 name을 사욯하지 않는 경우에도 sleep이 호출됨
class LazyPerson {
    val name: String

    init {
        Thread.sleep(2_000)
        name = "김수한무"
    }
}

backing property

  • name이 호출되지 않으면 backing property 초기화가 일어나지 않음
  • name이 호출되면 최초 한번만 초기화가 일어남
class LazyPerson {
    private var _name: String? = null // backing property
    val name: String
        get() {
            if (this._name == null) {
                Thread.sleep(2_000)
                this._name = "김수한무"
            }

            return this._name!!
        }
}

by lazy

  • backing property를 사용할때와 같은 동작을 하면서 간단하게 구현 가능
  • lazy blockname의 getter가 실행될때 최초 1번만 실행되며 Thread-safe 함
    class LazyPerson {
      val name: String by lazy {
          Thread.sleep(2_000)
          "김수한무"
      }
    }

응용 및 실제 상황 질문

  • 테스트 코드에서 lateinit을 사용하는 이유는 무엇인가요?
    • 테스트 코드 작성 시 lateinit을 사용하면 어떤 이점이 있나요?
  • 실제 프로젝트에서 lazy를 사용하는 예를 들어주세요.
    • 실제 프로젝트에서 lazy를 어떻게 사용해보았나요? 또는 사용한다면 어떤 상황에서 유용할까요?
  • lateinit과 lazy를 함께 사용할 수 있는 시나리오를 설명해주세요.
    • lateinit과 lazy를 함께 사용하는 시나리오가 있다면 어떤 경우일까요?
  • lateinit과 nullable 변수의 차이점
    • lateinit을 사용하는 것과 변수를 nullable하게 만드는 것의 차이점은 무엇인가요?
  • 초기화 비용이 큰 변수를 처리하는 방법
    • 초기화 비용이 큰 변수를 처리할 때 lateinit과 lazy 중 어떤 것을 선택해야 할까요? 그 이유는 무엇인가요?

9강. by lazy의 원리와 위임 프로퍼티

아래 코드를 좀더 재사용 가능한 구조로 변경 할 예정

class LazyPerson {
    private var _name: String? = null
    val name: String
        get() {
            if (_name == null) {
                Thread.sleep(2_000)
                this._name = "김수한무"
            }

            return this._name!!
        }
}
  • LazyInitProperty 클래스를 만들어 템플릿화 함
  • Personname getter가 호출되면 곧바로 LazyInitProperty의 getter가 호출됨
    • 위임 패턴
      위임패턴
class LazyPerson {
    private val delegateProperty = LazyInitProperty {
        Thread.sleep(2_000)
        "김수한무"
    }

    val name: String
        get() = delegateProperty.value
}

class LazyInitProperty<T>(val init: () -> T) {
    private var _value: T? = null
    val value: T
        get() {
            if (_value == null) {
                this._value = init()
            }

            return this._value!!
        }
}

by lazy 역시 완전히 동일한 원리!!

- `by`: 코틀린에 존재하는 키워드, `name`의 getter는 뒤 객체의 getter로 이어줌
class LazyPerson {
    val name: String by lazy {
        Thread.sleep(2_000)
        "김수한무"
    }
}
  • lazy함수는 Lazy()객체를 반환하므로 아래와 같이 변환됨
    public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
class Person {
    // by: name의 getter를 Lazy객체의 getter로 이어줌
    val name: String by Lazy()
}
  • by뒤에 위치한 클래스는 getValue 또는 setValue 함수를 가지고 있어야함.
    by lazy 호출 순서

getValue 자세히 알아보기

operator fun getValue(thisRef: R, property: KProperty<*>): T {
}
  • thisRef: 위임 프로퍼티를 가지고있는 클래스의 인스턴스 (Person)
  • property: 위임 프로퍼티 정보 (name)

LazyInitPropertygetValue 추가해보기

class LazyPerson {
    // getValue를 추가했으므로 by 키워드 사용가능
    val name: String by LazyInitProperty {
        Thread.sleep(2_000)
        "김수한무"
    }
}

class LazyInitProperty<T>(val init: () -> T) {
    private var _value: T? = null
    val value: T
        get() {
            if (_value == null) {
                this._value = init()
            }

            return this._value!!
        }

    operator fun getValue(thisRef: Any, property: KProperty<*>): T {
        return value
    }
}

setValue 자세히 알아보기

  • getValue와 거의 동일
  • setter에서는 값이 변경되니, 변경될 값을 파라미터로 받음
operator fun setValue(thisRef: R, property: KProperty<*>, value: T) {
}

마무리

질문 내용

by lazy에 대한 질문

  1. by lazy의 기본 개념을 설명해주세요.
    • by lazy 키워드는 무엇을 위해 사용되며, 어떻게 작동하나요?
  2. by lazy의 초기화 타이밍
    • by lazy로 선언된 변수가 언제 초기화되나요? 이 초기화는 몇 번 발생하나요?
  3. by lazy와 Thread-safety
    • by lazy의 기본 구현은 어떻게 thread-safe를 보장하나요?
  4. by lazy의 사용 예시
    • 실제 프로젝트에서 by lazy를 어떻게 사용해보았나요? 또는 사용한다면 어떤 상황에서 유용할까요?
  5. by lazy의 장점과 단점
    • by lazy를 사용하는 것의 장점과 단점은 무엇인가요?

위임 프로퍼티에 대한 질문

  1. 위임 프로퍼티의 기본 개념을 설명해주세요.
    • 위임 프로퍼티란 무엇이며, 어떻게 사용되나요?
  2. getValue 메소드의 역할
    • 위임 프로퍼티에서 getValue 메소드는 어떤 역할을 하나요?
  3. setValue 메소드의 역할
    • 위임 프로퍼티에서 setValue 메소드는 어떤 역할을 하나요?
  4. 위임 패턴의 이점
    • 위임 패턴을 사용하는 것의 이점은 무엇인가요? 예시를 들어 설명해주세요.
  5. by lazy와 위임 프로퍼티의 관계
    • by lazy와 위임 프로퍼티의 관계를 설명해주세요. 어떻게 연관되어 있나요?

코드 이해 및 적용 질문

  1. LazyInitProperty 클래스의 역할
    • LazyInitProperty 클래스는 어떤 역할을 하나요? 해당 클래스의 getValue 메소드는 어떻게 작동하나요?
  2. 위임 프로퍼티의 재사용성
    • LazyInitProperty 클래스를 사용하면 코드의 재사용성이 어떻게 향상되나요?
  3. by lazyLazyInitProperty의 차이점
    • by lazyLazyInitProperty를 사용하는 것의 차이점은 무엇인가요?
  4. 실제 상황에서의 적용
    • 실제 상황에서 위임 프로퍼티를 사용해본 경험이 있나요? 있다면 어떤 상황이었고, 어떻게 적용했나요?
  5. 위임 프로퍼티의 활용 예제
    • 위임 프로퍼티를 활용한 다른 예제 코드를 작성해보세요. 그 코드에서 getValuesetValue를 어떻게 구현할 수 있을까요?

10강. 코틀린의 표준 위임 객체

notNull()

  • lateinit과 비슷한 역할을 함
  • 아직 초기화되지 않았다면 IllegalStateException 발생
  • primity type에는 lateinit을 사용할 수 없지만 notNull()은 사용가능!
class LazyPerson2 {
    var age: Int by notNull()
}

observable()

  • observable 함수는 초기값과 onChange 함수를 받음
  • onChange함수는 setter가 호출될 때마다 호출됨
    // 함수 시그니쳐, 간략화한 코드
    public inline fun <T> observable(
      initialValue: T,
      onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit
    )
class LazyPerson2 {
    var age: Int by observable(20) { _, oldValue, newValue ->
        println("이전 값 : $oldValue, 새로운 값: $newValue")
    }
}

fun main() {
    val p = ObservablePerson()
    p.age = 30
}

// 출력 결과
// 이전 값 : 20, 새로운 값: 30

vetoalbe()

  • observable과 비슷하나 onChange함수가 Boolean을 반환
  • setter가 호출될때 onChange 함수가 true를 반환하면 변경 적용, false를 반환하면 이전값이 그대로 남음
// 함수 시그니쳐, 간략화한 코드
public inline fun <T> vetoable(
    initialValue: T,
    onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean
)
class VetoablePerson {
    // age가 0 이상일 경우만 셋팅됨
    var age: Int by vetoable(20) { _, _, newValue ->
        newValue > 0
    }
}

또 다른 프로퍼티로 위임하기

  • 사람의 나이를 num프로퍼티에 저장하고 있음
  • 프로퍼티의 이름을 변경하고 싶으나 호환성을 유지해야함
class AgePerson {
    var num: Int = 0
}
  • 프로퍼티 앞에 ::을 붙이면 위임객체로 사용 가능
  • 코드 사용자들이 age로 코드를 변경하면 그때 num을 제하면 됨
  • class AgePerson { @Deprecated("age를 사용하세요!", ReplaceWith("age")) var num: Int = 0 var age: Int by this::num }

map

  • getter가 호출되면 map["name"] 또는 map["age"]를 찾음
    • ex) MapPersonname프로퍼티 접근시 map에서 name키의 값을 가져오도록 위임
    • map에 name이나 age키값이 없다면 에러 발생
  • MutableMap사용시 val대신 var도 사용 가능
class MapPerson(map: Map<String, Any>) {
    val name: String by map
    val age: Int by map
}

질문 내용

notNull()에 대한 질문

  1. notNull()의 기본 개념을 설명해주세요.
    • notNull() 키워드는 무엇을 위해 사용되며, 어떻게 작동하나요?
  2. notNull()lateinit의 차이점
    • notNull()lateinit의 차이점은 무엇인가요?
  3. notNull()의 사용 예시
    • notNull()을 사용하는 실제 예시를 들어보세요. 어떤 상황에서 유용할까요?
  4. notNull()을 사용한 초기화 검사
    • notNull()을 사용한 변수가 초기화되지 않았을 때 발생하는 예외는 무엇인가요?
  5. notNull()을 primitive 타입에 사용할 수 있는 이유
    • notNull()은 primitive 타입에 사용할 수 있는지 설명해주세요.

observable()에 대한 질문

  1. observable()의 기본 개념을 설명해주세요.
    • observable() 함수는 무엇을 위해 사용되며, 어떻게 작동하나요?
  2. observable() 함수의 시그니처
    • observable() 함수의 시그니처와 매개변수를 설명해주세요.
  3. observable()의 사용 예시
    • observable()을 사용하는 실제 예시를 들어보세요. 어떤 상황에서 유용할까요?
  4. observable()의 onChange 함수
    • observable()onChange 함수는 언제 호출되며, 어떤 역할을 하나요?
  5. observable()을 사용한 상태 변화 추적
    • observable()을 사용하여 객체의 상태 변화를 추적하는 방법을 설명해주세요.

vetoable()에 대한 질문

  1. vetoable()의 기본 개념을 설명해주세요.
    • vetoable() 함수는 무엇을 위해 사용되며, 어떻게 작동하나요?
  2. vetoable() 함수의 시그니처
    • vetoable() 함수의 시그니처와 매개변수를 설명해주세요.
  3. vetoable()의 사용 예시
    • vetoable()을 사용하는 실제 예시를 들어보세요. 어떤 상황에서 유용할까요?
  4. vetoable()observable()의 차이점
    • vetoable()observable()의 차이점은 무엇인가요?
  5. vetoable()을 사용한 조건부 변경
    • vetoable()을 사용하여 조건부로 프로퍼티 값을 변경하는 방법을 설명해주세요.

또 다른 프로퍼티로 위임하기에 대한 질문

  1. 프로퍼티 위임의 기본 개념을 설명해주세요.
    • 프로퍼티를 다른 프로퍼티로 위임하는 것은 무엇을 의미하나요?
  2. 프로퍼티 위임의 사용 예시
    • 프로퍼티 위임을 사용하는 실제 예시를 들어보세요. 어떤 상황에서 유용할까요?
  3. 위임 프로퍼티의 장점
    • 프로퍼티를 위임해서 얻는 장점은 무엇인가요?
  4. 호환성을 유지하는 프로퍼티 위임
    • 호환성을 유지하면서 프로퍼티 이름을 변경하는 방법을 설명해주세요.
  5. 위임 프로퍼티의 @Deprecated 어노테이션
    • 위임 프로퍼티에 @Deprecated 어노테이션을 사용하는 이유는 무엇인가요?

map에 대한 질문

  1. map 위임의 기본 개념을 설명해주세요.
    • map 위임이란 무엇이며, 어떻게 작동하나요?
  2. map 위임의 사용 예시
    • map 위임을 사용하는 실제 예시를 들어보세요. 어떤 상황에서 유용할까요?
  3. map 위임의 장점과 단점
    • map 위임을 사용하는 것의 장점과 단점은 무엇인가요?
  4. MutableMap을 사용한 프로퍼티 변경
    • MutableMap을 사용하여 프로퍼티 값을 변경하는 방법을 설명해주세요.
  5. map 위임의 키와 값
    • map 위임에서 키와 값이 어떻게 매핑되는지 설명해주세요.

11강. 위임과 관련된 몇 가지 추가 기능

  • 위임 객체를 만들기위해 필요한것
    • getValue() / setValue()
  • 시그니처를 외우지 않고 ReadOnlyProertyReadWriteProperty를 사용

ReadOnlyProertyReadWriteProperty

  • getValue() / setValue()의 시그니처를 기억하지 않아도 됨
  • 익명 클래스를 사용할 수 있음
public fun interface ReadOnlyProperty<in T, out V> {
    public abstract operator fun getValue(thisRef: T, property: kotlin.reflect.KProperty<*>): V
}

public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> {
    public override operator fun getValue(thisRef: T, property: KProperty<*>): V
    public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
}

예시

  • getValue() / setValue()의 시그니처
  • class LazyInitProperty<T>(val init: () -> T): ReadOnlyProperty<Any, T> { ... override fun getValue(thisRef: Any, property: KProperty<*>): T { return value }

}


- 익명 클래스를 사용
```kotlin
class LazyInitProperty<T>(val init: () -> T): ReadOnlyProperty<Any, T> {
    ...

    val status: String by object: ReadOnlyProperty<Any, String> {
        private var isGreen = false
        override fun getValue(thisRef: Any, property: KProperty<*>): String {
            return if (isGreen) {
                "Happy"
            } else {
                "Sad"
            }
        }
    }
}

위임 프로퍼티와 위임 객체를 연결할 때 로직 끼워넣기

  • 예를들어 프로퍼니의 이름이 name일때만 정상 동작해야하는 위임객체가 필요
    class DelegateNamePerson {
      val name by DelegateProperty("최태현") // 정상 동작 O
      val country by DelegateProperty("한국") // 정상 동작 X
    }
    

class DelegateProperty(
private val initValue: String
): ReadOnlyProperty<Any, String> {
override fun getValue(thisRef: Any, property: KProperty<*>): String {
return initValue
}
}


### 방법1. `propertyName` 파라미터 추가
- 프로퍼티 명을 직접 넣어줘야 해서 불편
- 비슷한 요구사항이 나올때 마다 작업을 해줘야함
```kotlin
class DelegateNamePerson {
    val name by DelegateProperty("최태현", "name")
    val country by DelegateProperty("한국", "country")
}

class DelegateProperty(
    private val initValue: String,
    propertyName: String, // 추가
): ReadOnlyProperty<Any, String> {

    init {
        // property name 검사
        if (propertyName != "name") {
            throw IllegalArgumentException("name에서만 사용가능!")
        }
    }

    override fun getValue(thisRef: Any, property: KProperty<*>): String {
        return initValue
    }
}

방법2. provideDelegate 함수 사용

  • 어떤 객체가 provideDelegate를 가지고있으면 위임객체 대신 by뒤에 사용 가능

provideDelegate 역할 이미지

class DelegateNamePerson {
    val name by DelegateProvider("최태현")
    val country by DelegateProvider("한국")
}

// property와 delegate 사이의 중간다리 역할
class DelegateProvider(
    private val initValue: String,
): PropertyDelegateProvider<Any, DelegateProperty> {

    override fun provideDelegate(thisRef: Any, property: KProperty<*>): DelegateProperty {
        if (property.name != "name") {
            throw IllegalArgumentException("${property.name}은 안됩니다! name만 사용가능!")
        }

        return DelegateProperty(initValue)
    }
}

class DelegateProperty(
    private val initValue: String,
): ReadOnlyProperty<Any, String> {
    override fun getValue(thisRef: Any, property: KProperty<*>): String {
        return initValue
    }
}

위임클래스

  • 아래와 같이 Fruit인터페이스가 있고 Fruit를 구현한 Apple이 존재함
  • 여기서 GreenApple을 추가하려면 어떻게 해야할까?
    interface Fruit {
      val name: String
      val color: String
      fun bite()
    }
    

class Apple: Fruit {
override val name: String
get() = "사과"
override val color: String
get() = "빨간색"

override fun bite() {
    println("냠냠 사과")
}

}


### 방법1) `Fruit`을 구현한 `GreenApple` 클래스 생성
- `Apple`과 중복이 많음
    - `name`, `bite()`가 중복됨
```kotlin
class GreenApple: Fruit {
    override val name: String
        get() = "사과"
    override val color: String
        get() = "초록색"

    override fun bite() {
        println("냠냠 사과")
    }
}

방법2) Apple을 상속받아 GreenApple 클래스를 새로 구현

  • Apple을 불필요하게 상속받을 경우가 생길수 있음
  • 상속 보단 합성!
  • open class Apple: Fruit { override val name: String get() = "사과" override val color: String get() = "빨간색" override fun bite() { println("냠냠 사과") } }

class GreenApple2: Apple() {
// 필요한 부분만 재구성
override val color: String
get() = "초록색"
}


### 방법3) 합성 사용
- 방법2의 상속보다 코드양이 많아짐
```kotlin
class GreenApple3(private val apple: Apple): Fruit {
    override val name: String
        get() = this.apple.name

    override val color: String
        get() = "초록색"

    override fun bite() {
        this.apple.bite()
    }

}

방법4) 클래스 위임 사용

  • by키워드를 사용하여 클래스 위임을 사용가능 (합성을 편하게 사용 가능)
    class GreenApple3(private val apple: Apple): Fruit by apple {
      override val color: String
          get() = "초록색"
    }

질문내용

  • 기본적인 내용 확인
    • ReadOnlyProperty와 ReadWriteProperty의 기본적인 역할과 차이점은 무엇인가요?
    • getValue()와 setValue() 메서드의 시그니처를 외울 필요 없이 ReadOnlyProperty와 ReadWriteProperty를 사용하는 것이 어떤 점에서 유용한가요?
  • 코드 이해와 적용
    • LazyInitProperty 클래스에서 익명 클래스를 사용하여 프로퍼티를 정의할 때의 장점은 무엇인가요?
    • DelegateProperty와 DelegateProvider 클래스를 사용하는 예시에서, provideDelegate 함수의 역할은 무엇인가요?
    • DelegateProperty 클래스의 getValue() 메서드에서 initValue를 반환하는 방식 대신 다른 로직을 추가하려면 어떻게 해야 할까요?
  • 심화된 주제 탐구
    • 위임 프로퍼티와 위임 객체를 연결할 때 로직을 끼워넣는 두 가지 방법의 장단점은 무엇인가요?
    • DelegateProvider 클래스를 사용하여 특정 프로퍼티에만 위임 객체를 사용하도록 제한할 때 발생할 수 있는 문제점은 무엇인가요?
  • 위임 클래스와 합성
    • GreenApple을 구현할 때 상속 대신 합성을 사용하는 이유는 무엇인가요?
    • 합성을 사용할 때 코드량이 많아지는 단점에도 불구하고 합성을 선호해야 하는 이유는 무엇인가요?
    • 클래스 위임을 사용하여 GreenApple3 클래스를 정의할 때 Fruit 인터페이스의 다른 메서드를 재정의하지 않고도 Apple의 메서드를 활용할 수 있는 이유는 무엇인가요?
  • 실전 적용과 응용
    • 실무에서 provideDelegate를 사용할 수 있는 다른 예시에는 어떤 것들이 있을까요?
    • 클래스 위임을 사용하여 복잡한 객체의 기능을 확장하는 다른 예시를 들어 설명해 줄 수 있나요?

12강. Iterable과 Sequence (feat. JMH)

Collection

  • 우리는 데이터를 조작할때 Collection을 사용함
    • 이러한 Iterable은 연산의 각 단계마다 중간 Collection이 임시로 생성됨
  • 중간 Collection을 만들지 않는 방법을 없을까?
data class MyFruit(
    val name: String,
    val price: Long,
)

fun main() {
    val fruits = listOf(
        MyFruit("사과", 1000L),
        MyFruit("바난ㅏ", 3000L),
    )

    val avg = fruits
        .filter { it.name == "사과" }
        .map { it.price }
        .take(10_000)
        .average()
}

Sequence

  • 중간 Collection을 만들지 않고 데이터를 조작할 수 있음
  • asSequence()를 통해 만들수 있음
    val avg = fruits.asSequence()
        .filter { it.name == "사과" }
        .map { it.price }
        .take(10_000)
        .average()

Sequence의 동작원리

  • 각 단계(filter, map)가 모든 원소에 적용되지 않을수 있음
  • 한 원소에 대해 모든 연산을 진행하고 다음 원소로 넘어감
  • 최종 연산이 나오기전까지 계산 자체를 미리 하지 않음 (지연연산)

Sequence 동작방식

SequenceIterable보다 빠를까?

  • JMH 벤치마크 진행
  • 대용량일때 Sequence가 더빠름
  • Sequence는 연산 순서에 큰 차이가 날수 있음

질문 내용

  • 기본적인 내용 확인
    • Collection과 Iterable의 차이점은 무엇인가요?
    • Sequence를 사용하는 주된 이유는 무엇인가요?
  • 코드 이해와 적용
    • fruits 리스트에서 asSequence()를 사용했을 때와 사용하지 않았을 때의 차이점은 무엇인가요?
  • 심화된 주제 탐구
    • Sequence가 중간 Collection을 생성하지 않기 때문에 성능상 이점이 있는 이유는 무엇인가요?
    • Sequence의 동작 원리에서 지연 연산(lazy evaluation)이란 무엇인가요? 이 개념이 어떻게 성능을 향상시키나요?
  • 실전 적용과 응용
    • Sequence를 사용하지 않으면 filter와 map 연산에서 중간 Collection이 어떻게 생성되는지 설명해 주세요.
    • Sequence가 모든 상황에서 Iterable보다 더 나은 선택일까요? 어떤 경우에는 그렇지 않을 수 있을까요?
  • 벤치마크와 성능
    • JMH 벤치마크는 무엇이며, 왜 Sequence의 성능을 평가하는 데 유용한가요?
    • 대용량 데이터를 처리할 때 Sequence가 더 빠른 이유는 무엇인가요?
    • Sequence의 연산 순서에 따라 성능 차이가 발생할 수 있다고 했는데, 예시를 들어 설명해 주세요.
  • 일반적인 고려 사항
    • Sequence를 사용할 때 주의해야 할 점은 무엇인가요?
    • 작은 데이터셋에서도 Sequence를 사용하는 것이 좋은 선택일까요? 이유는 무엇인가요

위 내용은 인프런 코틀린 고급편강의를 시청하고 작성했습니다
인프런

 
728x90