코틀린으로 개발을 하다 보면 Sequence라는 타입을 종종 만나게 됩니다.
처음에는 익숙한 List나 Stream만 써도 충분한데, 갑자기 등장한 이 친구는 도대체 어떤 역할을 할까요? 그리고 왜 사용하는 걸까요?
이 글에서는 Sequence의 개념부터, Stream과의 차이점, 실제로 왜 속도가 빨라지는지도 함께 정리해보겠습니다.
Sequence란?
간단히 말하면 Sequence는 "지연 계산(Lazy Evaluation)을 지원하는 컬렉션 처리 방식"입니다. 일반적인 리스트 연산은 **즉시 계산(Eager Evaluation)**을 하는 반면, Sequence는 계산을 나중으로 미루는 특성이 있습니다.
val list = listOf(1, 2, 3, 4, 5)
val result = list
.map { it * 2 } // 즉시 처리
.filter { it > 5 } // 즉시 처리
.toList()
이 코드는 리스트를 순회하면서 map, filter가 각각 한 번씩 수행됩니다. 즉, 중간 리스트가 만들어지는 것이죠.
val result = sequenceOf(1, 2, 3, 4, 5)
.map { it * 2 } // 아직 실행 안 됨
.filter { it > 5 } // 아직 실행 안 됨
.toList() // 여기서 한 번에 실행됨
위 코드는 toList()를 호출할 때까지 실제로 아무 연산도 수행하지 않습니다. 필요할 때만 계산이 일어나는 것이 Sequence의 가장 큰 특징입니다.
Stream과 Sequence의 차이점
항목 Kotlin Sequence Java Stream
언어 지원 | Kotlin 표준 | Java 8 이상 |
중간 연산 처리 | Lazy (지연 처리) | Lazy (지연 처리) |
병렬 처리 | ❌ 기본 미지원 | ✅ 병렬 스트림 지원 |
단방향/재사용 | 재사용 가능 | 일회용 (한 번만 순회) |
사용 용도 | 간단한 성능 최적화 시 활용 | 대용량 데이터 처리에 적합 |
Sequence는 Java의 Stream과 유사하지만, 더 간단하게 재사용이 가능하고, Kotlin의 문법과 더 잘 어울립니다.
왜 Sequence가 더 빠르다고 느껴질까요?
Sequence는 중간에 생성되는 임시 리스트를 만들지 않기 때문에 메모리 효율이 좋고, 연산 수가 줄어들 수 있습니다. 특히 연산 순서가 중요한 경우, 효율 차이는 확실히 벌어집니다.
예제를 하나 보시죠.
val result = (1..1000000)
.asSequence()
.map { it * 2 }
.filter { it % 3 == 0 }
.take(5)
.toList()
이 경우, 일반 리스트 연산이면 100만 개를 모두 순회하고 나서야 결과가 나옵니다. 하지만 Sequence는 take(5)로 인해 필요한 값만 앞에서부터 계산하고 바로 종료되죠. 성능이 빨라지는 건 당연합니다.
언제 Sequence를 써야 할까요?
- 데이터 크기가 크고, 중간 리스트가 많이 생길 때
- map, filter, take, find 등 체이닝이 많은 경우
- 최종 결과가 부분 데이터만 필요할 때 (take, first 등)
반대로 데이터가 적고, 간단한 처리만 할 때는 굳이 Sequence를 쓰지 않아도 됩니다. 오히려 코드가 복잡해질 수 있어요.
마무리하며
Sequence는 성능 최적화를 위해 제공되는 Kotlin만의 강력한 기능입니다. 무조건 사용하는 게 아니라, 언제 쓰면 좋은지를 알고 잘 활용하는 것이 중요하다고 생각합니다. 리스트 연산이 많고 성능 이슈가 생겼을 때, 한 번쯤 Sequence를 고민해보셔도 좋습니다.
혹시 Java Stream에 익숙하셨다면 Kotlin의 Sequence도 큰 거부감 없이 사용할 수 있으실 거예요. 작은 코드가 큰 차이를 만들기도 하니까요!