공부/이펙티브코틀린

아이템 49 - 하나 이상의 처리 단계를 가진 경우네는 시퀀스를 사용하라

띵커베르 2024. 8. 18. 12:49
728x90

코틀린에서 컬렉션을 처리할 때, 다음과 같은 코드가 자주 사용됩니다:

val numbers = listOf(1, 2, 3, 4, 5)

val result = numbers
    .map { it * 2 }
    .filter { it > 5 }
    .map { it + 1 }

println(result) // [7, 9, 11]

 

해당 코드는 map, filter 가 연속적으로 호출된다. 이런 방식은 각 단계마다 새로운 리스트를 생성하므로, 처리 단계가 많아질수록 불필요한 리스트 생성이 발생하고 성능이 저하될 수 있다.

 

이러한 문제를 해결하기 위해 코틀린은 시퀀스를 제공한다.

시퀀스는 중간 처리 단계를 거칠 때마다 새로운컬렉션을 생성하지 않고, 필요할 때마다 요소를 하나씩 처리하는 lazy 방식을 사용한다.

val numbers = listOf(1, 2, 3, 4, 5)

val result = numbers.asSequence()
    .map { it * 2 }
    .filter { it > 5 }
    .map { it + 1 }
    .toList()

println(result) // [7, 9, 11]

 

시퀀스의 지연 처리는 다음과 같은 장점을 가진다.

  • 자연스러운 처리 순서를 유지
  • 최소한 연산
  • 무한 시퀀스 형태로 사용할 수 있다
  • 각각의 단계에서 컬렉션을 만들어 내지 않는다.

 

자연스러운 처리 순서를 유지

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5)

    // 일반 컬렉션 사용
    val normalResult = numbers
        .map {
            println("Mapping (normal): $it")
            it * 2
        }
        .filter {
            println("Filtering (normal): $it")
            it > 5
        }

    println("Normal Result: $normalResult")

    // 시퀀스 사용
    val sequenceResult = numbers.asSequence()
        .map {
            println("Mapping (sequence): $it")
            it * 2
        }
        .filter {
            println("Filtering (sequence): $it")
            it > 5
        }
        .toList()

    println("Sequence Result: $sequenceResult")
}

// 일반 컬렉션 사용시 처리 순서
Mapping (normal): 1
Mapping (normal): 2
Mapping (normal): 3
Mapping (normal): 4
Mapping (normal): 5
Filtering (normal): 2
Filtering (normal): 4
Filtering (normal): 6
Filtering (normal): 8
Filtering (normal): 10
Normal Result: [6, 8, 10]


// 시퀀스 사용시 처리 순서
Mapping (sequence): 1
Filtering (sequence): 2
Mapping (sequence): 2
Filtering (sequence): 4
Mapping (sequence): 3
Filtering (sequence): 6
Mapping (sequence): 4
Filtering (sequence): 8
Mapping (sequence): 5
Filtering (sequence): 10
Sequence Result: [6, 8, 10]

일반 컬렉션 은 map 연산이 전체 리스트에 대해 먼저 실행되고, 그 결과가 중간 컬렉션으로 저장된 후 filter 연산이 적용

시퀀스 는 각 요소에 대해 map 과 filter 연산이 순차적으로 적용됨. 이로 인해 코드가 작성된 순서대로 처리된다. 중간 컬렉션을 생성하지 않아 메모리 효율이 높아지고 처리순서가 자연스럽게 유지된다.

 

최소한 연산

시퀀스는 필요한 연산만 수행하여 불필요한 계산을 피한다.

 

 

무한 시퀀스 형태로 사용할 수 있다

일반 컬렉션은 한정된 크기를 가져야하지만, 시퀀스는 필요할 때만 요소를 생성하고 처리할 수 있으므로, 무한한 데이터 스트림을 다루는 데 적합하다.

 

  • 메모리 효율성: 무한 시퀀스는 모든 데이터를 한 번에 생성하지 않기 때문에, 메모리를 적게 사용합니다. 필요한 데이터만 즉시 생성되어 처리되므로, 큰 데이터를 다루거나 실시간 데이터를 처리할 때 유리합니다.
  • 게으른 평가(lazy evaluation): 시퀀스는 필요할 때만 요소를 생성하므로, 무한히 많은 데이터를 다루면서도 실제로 필요한 부분만 연산할 수 있습니다. 이를 통해 성능 최적화가 가능합니다.
  • 실시간 처리 가능: 센서 데이터, 로그 등 실시간으로 들어오는 무한 스트림을 처리할 때, 무한 시퀀스를 사용하면 자연스럽게 데이터를 처리할 수 있습니다.

 

 

각각의 단계에서 컬렉션을 만들어 내지 않는다

일반적으로 코틀린 map, filter 와 같은 연산은 각 단계마다 새로운 컬렉션을 생성하는데, 이는 메모리 사용량을 증가하고, 성능에 부담을 줄 수 있다.

시퀀스를 사용하면, 중간 단계에서 새로운 컬렉션이 생성되지 않는다.

각 요소는 필요할 때만 계산되어 전달되며, 최종 결과를 얻을 때만 전체 컬렉션이 만들어진다. 큰 데이터 셋을 처리할 때 메모리 효율성을 크게 개선함

// map과 filter 사이에 중간 컬렉션이 생성되지 않고, 최종적으로 필요한 시점에서만 toList()가 호출되어 결과가 생성
val result = listOf(1, 2, 3, 4, 5)
    .asSequence()
    .map { it * 2 }
    .filter { it > 5 }
    .toList()

 

 

 


시퀀스가 빠르지 않는 경우

컬렉션 전체를 기반으로 처리해야 하는 연산은 시퀀스를 사용해도 빨라지지 않습니다.

ex) sorted

 

728x90