우아한테크코스 테코톡
차리의 Stream
카테고리 : 우아한테크코스 테코톡
차리의 Stream
스트림
- 오라클 공식 문서에서는 순차 및 병렬적인 집계연산을 지원하는 연속된 요소라고 표현
- 모던 자바 인 액션에서는 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소로 정의
- 스트림은 자바에 국한된 경우가 아니라 컴퓨터 사이언스 전반에서 사용되는 용어이기도 하고 또 일반 명사이기도 하다
- 위키피디아에서는 이러한 스트림을 A Sequence of Data elements made available over time(시간이 지남에 따라 사용할 수 있게 된 일련의 데이터 요소)이라고 표현한다
- 한편 캠브릿지 사전에서는 일반 명사로 A continuous flow of things or people(사물이나 사람의 지속적인 흐름)을 의미한다고 한다
- 즉, 스트림은 어떠한 요소들이 모인 하나의 고정된 집합이라고 생각하기 보다 flow, 즉 데이터 흐름이라는 것에 초점을 맞춰서 생각하면 스트림을 이해하기 좀 더 편할 것 같다
- 오라클 공식 문서에서는 스트림 패키지를 요소들의 스트림에 함수형 스타일의 연산을 지원하는 클래스들이라고 묘사
스트림 구조
- 스트림을 크게 세 파트로 분류하면 생성, 가공, 소비로 분류할 수 있다
- 생성
- 리스트, 맵과 같은 컬렉션으로부터 생성할 수 있다 하지만 이외에도 배열로부터 생성할 수도 있고 또 파일로부터 생성할 수도 있다
- 또 컬렉션과 다르게 무한을 만들 수도 있고 별도의 서드파티 라이브러리를 활용할 수도 있다
- 가공
- 다음 중간연산자는 필터, 맵, 리밋 등 소스로부터 얻어낸 값들을 이제 입맛대로 가공하는 역할을 한다
- 이런 중간연산자의 결과로는 새로운 스트림을 반환하는데 이는 곧 lazy evaluation을 할 수 있도록 만들어 준다
- 스트림에서 lazy evaluation은 최종 연산이 들어오기 전까지 중간 연산은 실제로 실행되지 않음을 의미한다 즉 새로운 스트림 인스턴스를 돌려주기만 할 뿐 어떠한 작업도 하지 않는다
- 이러한 lazy evaluation이 있기 때문에 루프 퓨전과 쇼트 서킷이라는 테크닉을 활용할 수 있다
- 모든 요소가 각 단계를 한 번에 거치는 게 아니라 개별적인 요소가 하나씩 단계를 순차적으로 거쳐가는 모습이 반복문이 합쳐진 것 같다고 해서 이제 루프 퓨전이라고 표현한다
- 다음 쇼트 서킷은 일련의 논리연산을 진행할 때 모든 연산을 수행하지 않고 결과가 확실한 경우에 나머지 연산을 수행하지 않는 것을 의미한다
- 중간연산자는 두 가지로 구분할 수 있는데 하나는 stateless 다른 하나는 stateful이다
- stateless는 특정 행위를 수행할 때 다른 요소에 대해서 독립적으로 처리될 수 있음을 의미한다
- 맵의 경우 맵 연산을 수행하기 위해서는 내가 현재 바라보고 있는 값에만 신경을 쓰면 된다
- 반대로 stateful의 경우 선행된 연산에 영향을 받는다
- sorted, distinct와 같은 중간연산자는 중간연산자가 실행되려면 자기 자신 이외의 값은 무엇이었는지 등에 대해서 특정 상태를 알고 신경을 써야 한다
- 소비
- 최종 연산자는 결과를 생성하거나 사이드 이펙트를 만들기 위해서 사용된다
- collect나 findAny, findFirst 등이 있다
- 특히 collect의 경우 스트림패키지 내부에 있는 collectors라는 것을 이용하면 활용성이 정말 다양하다
- 최종연산자가 수행되고 나면 스트림 파이프라인은 소비된 것으로 간주된다 즉, 다시 사용하려면 새로운 스트림을 만들어야 한다
- forEach는 대게 로그나 디버깅을 위한 출력으로만 사용할 것을 권장하고 또 이펙티브 자바에서는 forEach가 “덜 스트림스럽다”라고 이야기한다
- forEach 내부에서 값을 할당 하는 식으로 사용할 경우 불필요한 사이드 이펙트를 만들어 내는 셈이 된다
- 병렬처리가 추가될 경우에는 올바른 결과값을 기대하기도 어렵다
- 안정적인 collect라는 메서드를 활용할 수 있는데 굳이 리스크를 감수할 필요는 없다
- 따라서 forEach와 같이 사이드 이펙트를 통해서만 동작할 수 있는 연산들은 신중하게 사용을 해야 한다
스트림 장점
- 가독성이 좋다
- 코드의 변경이 쉽다 즉 유연하다
- 병렬처리를 간단하게 해결할 수 있다
- 간단하게 parallelStream 혹은 parallel 이라는 키워드를 붙여주는 것만으로 병렬처리를 지원
- 스트림의 병렬처리는 내부적으로 포크조인 프레임워크를 활용
- 병렬성은 stateless, stateful 이외에도 순서를 고려해야 할지 말지에 대한 여부나 병렬처리에 대한 오버헤드 등 여러 가지 신경 써야 할 것들이 많다
스트림 단점
- 컴퓨팅 비용
- 스트림은 내부적으로 다양한 조건과 상황에 따라서 연산을 처리하기 때문에 생성 하는데 적지 않은 비용이 발생한다
- 만약 주식 매매와 같이 성능이 코어한 이슈라면 스트림의 사용을 한번 더 고려해봐야 한다
- 인지에 대한 비용
- 기존 반복문의 경우 우리가 직접 값을 꺼내와서 사용했고 반대로 스트림을 사용할 경우 스트림 내부에서 연산이 일어난다 이를 각각 외부 반복과 내부 반복이라고 표현하는데 내부 반복이라는 것 자체가 GOF 책에서 등장하는 디자인 패턴 중 하나이기도 하고 그 자체로 단점으로 보기는 어려운데 문제는 코드가 정상적으로 동작하지 않을 경우 내부에서 반복이 어떻게 동작하는지 명확하게 알고 있어야 한다