[Kotlin in Action] 5.3. 지연 계산(lazy) 컬렉션 연산
map이나 filter 같은 몇가지 컬렉션 함수는 결과 컬렉션을 즉시 생성한다.
people.map(Person::name).filter { it. startsWith("A") }
위와 같은 예제에서 filter나 map은 리스트를 2개 만들게 된다. 원본 리스트에 원소가 2개 밖에 없다면 리스트가 2개 더 생겨도 큰 문제가 되지 않겠지만, 원소가 수백만 개가 되면 훨씬 더 효율이 떨어진다.
이를 더 효율적으로 만들기 위해서는 각 연산이 컬렉션을 직접 사용하는 대신 시퀀스를 사용하게 만들어야 한다.
people.asSequence() // 원본 컬렉션을 시퀀스로 변환한다.
.map(Person::name)
.filter { it.startsWith("A") }
.toList() // 결과 시퀀스를 다시 리스트로 변환한다.
이러한 코드는 중간 결과를 저장하는 컬렉션이 생기지 않기 때문에 원소가 많은 경우 성능이 눈에 띄게 좋아진다.
코틀린 지연 계산 시퀀스는 Sequence 인터페이스에서 시작한다. 이 인터페이스는 단지 한 번에 하나씩 열거될 수 있는 원소의 시퀀스를 표현할 뿐이다. Sequence 안에는 iterator라는 단 하나의 메소드가 있다. 그 메소드를 통해 시퀀로부터 원소 값을 얻을 수 있다.
Sequence 인터페이스의 강점은 그 인터페이스 위에 구현된 연산이 계산을 수행하는 방법 때문에 생긴다. 시퀀스의 원소는 필요할 때 비로소 계산된다. 따라서 중간 처리 결과를 저장하지 않고도 연산을 연쇄적으로 적용해서 효율적으로 계산을 수행할 수 있다.
asSequence 확장 함수를 호출하면 어떤 컬렉션이든 시퀀스로 바꿀 수 있다. 시퀀스를 리스트로 만들 때는 toList를 사용한다.
큰 컬렉션에 대해서 연산을 연쇄시킬 때는 시퀀스를 사용하는 것을 규칙으로 삼자.
5.3.1 시퀀스 연산 실행: 중간 연산과 최종 연산
시퀀스에 대한 연산은 중간 연산과 최종 연산으로 나뉜다. 중간 연산은 다른 시퀀스를 반환한다. 그 시퀀스는 최초 시퀀스의 연산을 변환하는 방법을 안다. 최종 연산은 결과를 반환한다. 결과는 최초 컬렉션에 대해 변환을 적용한 시퀀스로부터 일련의 계산을 수행해 얻을 수 있는 컬렉션이나 원소, 숫자 또는 객체다. 이 최종 연산을 호출해야 연기됐던 모든 계산이 수행된다.
자바 스크림과 코틀린 시퀀스 비교
둘의 개념은 같다. 그런데 코틀린에서 같은 개념을 따로 구현해 제공하는 이유는 자바 8 버전 이전에는 스트림이 없었기 때문이다.
5.3.2 시퀀스 만들기
asSequence()가 아닌, 시퀀스를 만드는 다른 방법으로는 generateSequence 함수를 사용할 수 있다. 이 함수는 이전의 원소를 인자로 받아 다음 원소를 계산한다. 다음은 generateSequence로 0부터 100까지 자연수의 합을 구하는 프로그램이다.
// 자연수의 시퀀스를 생성하고 사용하기
>>> val naturalNumbers = generateSequence(0) { it + 1 }
>>> val numbersTo100 = naturalNumbers.takeWhile { it <= 100 }
>>> println(numbersTo100.sum()) // 모든 지연 연산은 "sum"의 결과를 계산할 때 수행된다.
5050
이 예제에서 naturalNumbers와 numbersTo100은 모두 시퀀스며, 연산을 지연 계산한다. 최종 연산을 수행하기 전까지는 각 숫자는 계산되지 않는다. (여기서는 sum이 최종 연산)