- 컬렉션(Collection)을 활용하면 데이터를 그룹화하고 처리할 수 있다. ⇒ 너무 익숙하다,,
Collection
과Stream
의 차이를 정리하여 표로 정리해보았다.
Collection | Stream | |
---|---|---|
동작 형식 | 명령형 | 선언형 |
병렬성 | 가능한데, 복잡 | 간단(선언형) |
유연성 | 복-붙 | 함수 파라미터화 |
특징 | - Sequantial, Random Access - 데이터 표현에 집중 - 외부 반복(반복문을 직접 작성) | - Sequantial Access - 데이터 처리에 집중 - 내부 반복(반복문 작성 X) - 파이프 라이닝 |
4.1 스트림이란 무엇인가?
- 스트림을 사용하면 선언형으로 컬렉션 데이터를 처리할 수 있고, 데이터를 투명(transaparent)하게 병렬적으로 처리할 수 있다.
- 선언형으로 코딩을 할 경우의 장점은 새로운 기능을 구현할 때 기존의 기능들을 조합하여 구현할 수 있으므로 기존 코드를 복-붙하지 않아도 구현이 가능하다. 가독성을 얻는건 덤이다.
- 아래 그림처럼 파이프라인을 구성하여 복잡한 데이터 처리를 간편하게 할 수 있다.
![](https://blog.kakaocdn.net/dn/bi7Q3w/btslxLJcrkl/mV2kUvk9dPERYsU10T4NIK/img.png)
스트림의 특징
- 선언형: 더 간결하고 가독성이 좋아진다.
- 조립가능성: 유연성이 더 좋아진다.
- 병렬화: 성능이 좋아진다 (무조건 좋아지는건 아니다. reducing작업시에는 다른 thread를 기다릴 필요 없으므로 확실히 빠르겠지만 collect작업시에는 다른 thread를 기다려야하므로 더 느려질 수도 있다. 성능이 중요한 로직에서 사용할 경우 성능테스트를 꼭 해보자)
- Collection과 달리 무제한의 요소를 처리할 수 있다.
4.2 스트림 시작하기
- 숫자 범위나 I/O 자원에서 stream 요소를 만들 수 있다(⇒ 프론트할 때 많이 해봤다)
- 정확히 스트림이란, 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소로 정의된다.
- 연속된 요소
- 컬렉션도 연속된 요소이지만, 컬렉션은 자원의 저장과 시,공간적 복잡성에 대한 이슈가 주 도메인이다.
- 스트림은 표현적인 계산식이 주 도메인이다.
- 소스
- 스트림은 컬렉션, 배열, I/O자원 등의 데이터 제공 소스로부터 데이터를 소비한다.
- 데이터 처리 연산
- filter, map, reduce, find, match, sort등으로 데이터를 조작할 수 있다.
- 순차적으로 실행할지, 병렬적으로 실행할지는 선택할 수 있다.
- 연속된 요소
스트림의 특징
- 파이프 라이닝
- 대부분의 스트림연산은 스트림끼리 연결할 수 있도록 스트림 자신을 반환한다. 그 덕분에 lazy와 shortcircuit같은 최적화를 얻을 수 있다.
- 내부 반복
- for loop을 직접 돌리는 “외부 반복”과 달리 stream API내부에 반복문이 있어서 프로그래머가 작성할 필요가 없다.
4.3 스트림과 컬렉션
- 컬렉션과 스트림 모두 “연속된(sequential)”이라는 표현을 사용하는데, 의미가 서로 다르다.
- 스트림에서 연속되다의 의미는 순차적으로 값에 접근한다는 의미이다. 그 말인 즉슨, 한번 접근한 값을 두 번 다시는 접근할 수 없다.
- 컬렉션은 모든 값을 메모리에 올려두고 처리하는 반면, 스트림은 그 시점에 읽고 있는 데이터만을 메모리에 돌리게 된다. 따라서 무한한 길이의 수 처리도 가능하다.
- 스트림이 lazy하다는 말은, 사용자가 요청을 할 때(on-demand)만 데이터를 계산한다는 뜻이다. 반면 컬렉션은 사용자가 요청하지 않아도 이미 데이터가 생성되어 있다.
딱 한 번만 탐색할 수 있다.
- Iterator와 마찬가지로 스트림도 한 번만 탐색할 수 있다. 이전에 탐색했던 요소에 접근하려면 새로운 스트림을 만들어 접근해야한다.
외부 반복과 내부 반복
- 사용자가 직접 요소를 반복(for-loop)하는 것을 외부 반복이라한다.
- 스트림 API는 반복을 알아서 처리하고, 결과 스트림값을 어딘가에 저장해주는 내부 반복을 사용한다.
- 외부 반복의 단점은 병렬성을 반드시 스스로 관리해야한다는 점이다.
- 반면 내부 반복은 이런 병렬성을 내부적으로 하드웨어에 따라 다르게 구현하여 관리해주기 때문에 사용자가 신경쓰지 않아도 된다(라고 하고 써야한다..)
4.4 스트림 연산
- 스트림 연산은 크게 2가지로 구분할 수 있다. 중간 연산과 최종 연산이다.
중간 연산
- 중간 연산은 반환값이 스트림 자기 자신이다. 따라서 이들을 chaining하여 연산할 수 있고, 이 모습이 마치 파이프라인같다고하여 파이프라이닝이라 한다.( builder패턴과 비슷하다! )
- 중간 연산의 더 중요한 특징은, 최종 연산을 만날 때 까지 아무 연산을 수행하지 않는다는 것이다. 즉, Lazy하다는 뜻이다. 중간 연산을 모두 합친 다음, 최종 연산을 만나면 그것을 수행한다.
- 또한 ShortCircuit이라는 기법 덕분에
limit
같은 연산을 사용하여 개수를 제한하게 되면, stream전체에 연산을 적용하는 것이 아니라limit
걸어준 일부에만 적용하게 된다.
- 또한 Lazy하게 동작하기 위해 서로 다른 여러 연산을 한 과정으로 병합하는 것을 루프 퓨전(Loop Fusion)이라고 한다.
최종 연산
- 스트림 파이프라인에서 결과를 도출하는 연산이다. 즉, 스트림이외의 결과가 반환된다.
- 스트림 파이프라인은 최종 연산을 만나야 쌓여있던 중간 연산을 합쳐서 처리하게 된다.
스트림 이용하기
- 스트림을 사용하는 과정을 세 가지 과정으로 요약할 수 있다.
- 연산을 수행할 데이터 소스(컬렉션, IO)
- 스트림 파이프라인을 구성할 중간 연산 연결
- 스트림 파이프라인을 실행하고 결과를 만들 최종 연산
4.5 로드맵
은 생략.
4.6 요약
- 스트림은 소스에서 추출된 연속 요소로, 데이터 처리 연산을 지원한다.
- 스트림은 내부 반복을 지원한다. 내부 반복은
filter
,map
,sorted
등의 연산으로 반복을 추상화한다.
- 스트림의 중간 연산과 최종 연산이 있다.
- 중간 연산은 filter와 map처럼 스트림을 반환하면서 다른 연산과 연결되는 연산이다. 중간 연산을 이용해서 파이프라인을 구성할 수 있지만 중간 연산으로는 어떤 결과도 생성할 수 없다.
- forEach나 count처럼 스트림 파이프라인을 처리해서 스트림이 아닌 결과르 반환하는 연산을 최종 연산이라고 한다.
- 스트림의 요소는 요청할 때 Lazy하게 계산된다.
Uploaded by N2T
(23.05.31 23:36)에 작성된 글 입니다.