PROGRAMMING LANGUAGE/KOTLIN

[Kotlin] Collections의 Iterable과 Sequences 차이점

EARTH_ROOPRETELCHAM 2023. 8. 23. 23:04
728x90
반응형

들어가기 전에

Java에서는 stream()을 통해 collection 요소를 하나씩 참조하여 반복적인 작업을 할 수 있습니다.

Kotlin에는 Collections 자체에 filter(), map() 등의 API를 제공하고 있어 stream() 없이도 collection 요소에 하나씩 참조가 가능합니다.

위 내용만 보면, Java에서 collection에 stream() 붙여서 사용하는 경우와 Kotlin에서 collection의 API를 호출하는 경우 동일하게 연산이 이루어질 것 같지만 서로 상이하게 연산을 진행합니다.

  • Java의 stream: Lazy Evaluation
  • Kotlin의 collection 기본 연산(iterable): Eager Evaluation

이번 포스팅에서는 Kotlin의 collection 연산을 Java의 stream처럼 lazy evaluation 할 수 있는 Sequences() 연산과 Kotlin Collection의 기본 연산인 Iterable()에 대해 알아보고자 합니다.

Lazy Evaluation과 Eager Evaluation

Eager 연산(Iterable)과 Lazy 연산(Sequence)

Lazy Evaluation

listOf(1,3,5,7,2,4,6,8)
         .asSequence()
         .filter {
             print("filter: $it\n")
             it < 5
         }
         .map {
             print("map: $it\n")
             it * it
         }
         .toList()

// 호출 결과
filter: 1
map: 1
filter: 3
map: 3
filter: 5
filter: 7
filter: 2
map: 2
filter: 4
map: 4
filter: 6
filter: 8
  • 필요하지 않은 연산은 하지 않는 것으로, 여러 collection API가 체이닝되어 있을 때 collection 각 요소별로 체이닝된 API를 돌면서 실제 사용할 API까지만 호출하는 것입니다.

Lazy 연산 시, 종료 함수가 호출되지 않는 경우

  • Lazy Evaluation은 연산을 바로 수행하지 않고 실제 사용되기 전까지 미룹니다. 위 경우와 같이 결과를 반환하는 종료 함수(예: toList())가 없을 때에는 collection 연산을 수행하지 않습니다.

Eager Evaluation

listOf(1,3,5,7,2,4,6,8)
         .filter {
             print("filter: $it\n")
             it < 5
         }
         .map {
             print("map: $it\n")
             it * it
         }
         .toList()

// 호출 결과
filter: 1
filter: 3
filter: 5
filter: 7
filter: 2
filter: 4
filter: 6
filter: 8
map: 1
map: 3
map: 2
map: 4
  • 여러 collection API가 체이닝되어 있을 때 collection 원소들에 대해 API를 모두 호출한 후 다음 API를 호출하는 방식으로 진행합니다.

Eager 연산 시, 종료 함수가 호출되지 않은 경우

  • Eager Evaluation은 Lazy Evaluation과 다르게 매 체이닝 함수를 수행할 때마다 결과 collection을 반환합니다.

Sequences를 항상 사용해야 할까?

Sequences를 사용하는게 항상 좋은 것은 아니다

Kotlin 문서의 Sequences 설명을 보면, 위와 같은 내용이 있습니다.

더보기

sequences는 각 연산의 즉각적인 결과를 만드는 것은 피하게 되어 체이닝 연산 시 성능 향상을 기대할 수 있습니다. 다만, lazy 연산은 작은 collections 연산이나 간단한 계산을 할 때에는 오히려 오버헤드가 추가될 수 있기 때문에 Sequence와 Iterable 중 더 나은 선택을 고민하며 사용해야 합니다.

예를 들어, 아래와 같이 여러 체이닝 연산을 통해 나온 결과물 중 일부(any() 또는 take() 등)를 반환하는 경우라면 Sequence를 사용하는게 이점이 있을 수 있지만, 결국 체이닝 연산 대상이 되는 모든 결과물을 반환해야 한다면 sequences 사용이 오히려 오버헤드가 클 수 있다는 생각이 듭니다.

체이닝 연산을 모두 통과한 대상 중 일부만 반환하는 경우

Iterable 사용 예
Sequences 사용 예

Sequences 연산을 사용해도 Iterable 사용시와 연산 횟수가 동일한 예시

Sequences 사용(왼쪽)과 Iterable 사용(오른쪽)

참고 자료

728x90
반응형