티스토리 뷰

kotlin

코틀린의 collection, sequence, stream

LichKing 2019. 10. 27. 18:36

1. collection

val evens: List<Int> = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
            .filter { it % 2 == 0 }

코를린에서 처음 이런 코드를 봤을때 아무 생각없이 난 이렇게 생각했었다.

'오 코틀린은 stream 호출안해도 알아서 stream 으로 바꿔주나보다'

이렇게 생각한 근거는 딱히 없다. 그냥 stream 과 동일한 API 를 제공하고있어서 그렇게 생각했던것 같다. (많이 알겠지만 filter, map 과 같은 api들은 매우 범용적인 네이밍이라 프로그래밍 언어 차원을 넘어서서 여기저기서 많이 제공하고있는 명칭이다.)

 

하지만 그게 아니란걸 알게되어 이 포스팅을 작성하게됐다. 사실 신중히 고민해봤다면 이상한걸 느꼈어야한다. stream 으로 변환하는 코드를 호출한 적도 없고, (이게 자동으로 stream 으로 변환된거라면) 연산 결과를 다시금 list 로 변환하는 코드도 없다.

 

결과부터 얘기하자면 코틀린의 collection 에서 호출하는 map, filter 와 같은 API 들은 알아서 stream 으로 변환하는게 아니라 collection 자체에 추가된 확장 함수들이다. 자바에서 제공 API 가 아닌것이다. 뭐 좀 잘못알고있던 점은 있었으나 이렇든 저렇든 편리하게 collection 에서 바로 제공해주니 잘 쓰면 될것이다. 다만 코틀린에서 collection 에 추가한 확장 함수들은 기존 stream 과는 크게 다른점이 있다. 바로 stream 의 최적화 기술인 lazy, short circuit 이 적용되지않는다는 점이다. (해당 내용에 대해서는 이 포스팅에서 확인하자.)

https://multifrontgarden.tistory.com/128

 

Java8#04. 스트림(Stream)

개인적으로 자바8의 꽃이라 생각하는 스트림 포스팅이다. 내용이 워낙 방대하긴하나 쉽게 생각하면 스트림은 결국 API들의 모임이기때문에 외워야할게 많기도하다. 1. 스트림(Stream) 스트림은 자바8에 추가된 API..

multifrontgarden.tistory.com

listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
            .filter {
                println(it)
                it % 2 == 0
            }
            .find { it == 2 }

1 ~ 10부터의 리스트에서 2의 짝수인것들만 걸러내고 2를 찾는 조금은(아니 아주 많이) 이상한 코드이다. 하지만 정상적으로 돌아가는 코드이니 결과를 예측해보자. 정상적으로 최적화가 되어있다면 2를 찾는 것이므로 filter() 내에 있는 출력문은 1과 2만 출력해야한다. 하지만 지금 이 코드는 모든 원소를 출력한다. 한번 순회를 돌며 모든 원소에 대해 filter() 연산을 진행하고, 그 결과로 다시 리스트를 만든다. (2, 4, 6, 8, 10) 그리고 이 리스트에서 find() 를 실행하게되는것이다. 일단 10까지 갈 필요가 없는데 filter() 에서 10까지 순회하는것 자체도 문제이고, 그 결과로 다시 리스트를 만드는것도 문제이다. 그리고 코틀린에서 제공하는 collection 확장 함수들은 크기가 충분히 작은 경우에만 편리하게 사용하는 것이 좋다.

 

2. sequence

코틀린에서 제공하는 sequence 는 자바의 stream 과 거의 동일하다. 거의 동일함에도 따로 제공하는 이유는 코틀린은 자바8 미만도 지원하기때문이다. 자바8 이상을 하고있다면 sequence 를 사용할 이유는 거의 없지 않을까 싶다. collection 에서 asSequence() 를 호출해주면 손쉽게 collection 을 sequence 로 변환할 수 있다. 최적화되지않은 위 collection 코드를 sequence 로 처리해보자.

listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
            .asSequence()
            .filter {
                println(it)
                it % 2 == 0
            }
            .find { it == 2 }

처음 우리가 기대했던 대로 1,2 만 출력하고 종료된다. 다만 sequence 는 말그대로 sequence 이고, collection 이 아니기때문에 연산결과 요소들을 다시금 collection 으로 모아야한다면 해당 코드를 작성해주어야한다.

val evens: List<Int> = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
            .asSequence()
            .filter {
                println(it)
                it % 2 == 0
            }
            .toList()

생성하는 방법도, 연산결과를 다시 모으는것도 stream 과 거의 동일하다. 최종 연산이 없을경우 중간 연산을 진행하지않는 등의 기법들도 동일하게 적용되어있다.

 

3. stream

익히 알고있던 stream이다. sequence 가 존재하는 코틀린에서는 둘중에 어떤걸 써도 별문제가 없다. 내가 모든 API 를 꿰고있는건 아니지만 sequence 와 stream 사이에 약간 다른 API 들이 있어 필요한 API 를 제공하는걸 쓰면 된다. 다만 한가지 큰 차이가 있는데 stream 은 손쉬운 병렬처리 기능을 제공한다는 점이다. parallelStream 을 써야하는 경우가 있다면 당연히 stream 을 써야한다. 다만 parallelStream 은 주의해야할 점들이 있으니 그부분들을 주의해서 사용해야 할것이다.

 

https://multifrontgarden.tistory.com/254

 

parallelStream 남용으로인한 장애경험기

ParallelStream java8에 추가된 parallelStream 은 멀티스레드 프로그래밍을 매우 쉽게 해준다. 개발자가 직접 스레드 혹은 스레드풀을 생성하거나 관리할 필요없이 parallelStream(), parallel() 만 사용하면 알..

multifrontgarden.tistory.com

 

댓글
댓글쓰기 폼