본문 바로가기
📚 Kotlin

[Effective Kotlin] 아이템 49. 하나 이상의 처리 단계를 가진 경우에는 시퀀스를 사용하라

by GroovyArea 2023. 2. 4.

코틀린도 마찬가지로 Collection 처리를 위한 다양한 함수를 지원한다.

대표적으로 iterable 에서도 filter, map 등 동일한 함수를 지원하면서, 편리하게 사용할 수 있다.

 

하지만 지연 연산을 위해서는 자바와 같은 Stream, 코틀린에서는 Sequence를 사용하는 것이 좋다고 한다.

정리해보자.

 

Iterable과 Sequence

public inline fun<T> Iterable<T>.filter(
	predicate: (T) -> Boolean
): List<T> {
	return filterTo(ArrayList<T>(), predicate)
}

public fun <T> Sequence<T>.filter(
	predicate: (T) -> Boolean
): Sequence<T> {
	return FilteringSequence(this, true, predicate)
}

iterable의 filter와 sequence의 filter는 둘 다 동일한 중간 연산이지만, 반환하는 객체가 다르다.

 

또, 일반적인 컬렉션 처리 연산은 호출할 대 연산이 이루어진다.

반면, 시퀀스는 최종 연산(toList 등등)이 일어날 때까지 어떠한 연산도 일어나지 않는다.

 

시퀀스 지연 처리의 장점

  • 자연스러운 처리 순서를 유지
  • 최소한만 연산
  • 무한 시퀀스 형태 사용 가능
  • 각각의 단계에서 컬렉션을 만들지 않음
        sequenceOf(1, 2, 3)
            .filter { print("F$it, "); it % 2 == 1 }
            .map { print("M$it, "); it * 2 }
            .forEach { print("E$it, ") }
    // F1, M1, E2, F2, F3, M3, E6,
    
    listOf(1, 2, 3)
            .filter { print("F$it, "); it % 2 == 1 }
            .map { print("M$it, "); it * 2 }
            .forEach { print("E$it, ") }
    // F1, F2, F3, M1, M3, E2, E6,

시퀀스의 처리는 요소 하나하나당 지정한 연산을 한꺼번에 처리한다. => element-by-element order

이터러블은 요소 전체를 대상을 대상으로 연산을 차근차근 적용한다. => step-by-step order

 

시컨스의 처리는 내부적으로 고전 반복문을 사용하므로, 훨씬 빠르고 자연스러운 처리이다.

로우 레벨 컴파일러의 최적화가 진행되면, 처리가 훨씬 빨라질 것을 기대할 수 있다.

 

 

최소 연산

모든 것을 요소 전체에 적용하는 이터러블과 달리, 중간 연산이 있는 시퀀스는 원하는 요소에만 연산을 적용할 수 있다.

 

(1..10).asSequence()
            .filter { print("F$it, "); it % 2 == 1 }
            .map { print("M$it, "); it * 2 }
            .find { it > 5 }
            
            // F1, M1, F2, F3, M3,
            
(1..10)
            .filter { print("F$it, "); it % 2 == 1 }
            .map { print("M$it, "); it * 2 }
            .find { it > 5 }
            
            // F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, M1, M3, M5, M7, M9

중간 처리 단계를 모든 요소에 적용할 필요가 없는 경우에도 시퀀스가 이득이다.

요소를 선택할 연산은 first와 take를 자주 사용하는 것이 좋다고 한다.

 

 

각 단계에서 컬렉션을 만들지 않는다.

표준 컬렉션 처리 함수는 각 단계 별로 새로운 컬렉션을 만든다.

일반적으로 List를 새로 만들어 반환한다.

이는 컬렉션 객체가 생성되므로, 비용이 발생하고 그만큼 무겁고 느리다는 의미이다.

 

무거운 컬렉션을 처리할 때 특히 위험하다.

잘못하면 OOM이 터질 수도 있다.

 

반면, 시퀀스는 시퀀스를 반환하기 때문에,

이러한 걱정을 하지 않아도 된다.

 

 

정리

컬렉션과 시퀀스는 같은 처리 함수를 지원하고, 사용 형태가 비슷하지만, Lazy 하게 처리된다는 시퀀스의 장점은 넘사벽이다.

앞으로 자바의 Stream 처럼 코틀린에서는 Sequence를 자주 사용할 수 있을 것 같다.

반응형