4. Chapter4 - 스트림 소개


컬렉션은 자바에서 가장 많이 사용하는 기능 중 하나.

거의 모든 자바 어플리케이션은 컬렉션을 만들고 처리하는 과정을 포함.

컬렉션으로 데이터를 그룹화하고 처리 가능.

대부분의 자바 어플리케이션에서 컬렉션을 많이 사용하지만 완벽한 컬렉션 관련 연산을 지원 하기에는 부족.


1. 스트림이란 무엇인가?

- 선언형으로 컬렉션 데이터 처리가 가능한 기능.

> 선언형: 데이터를 처리하는 임시 구현 코드 대신 질의로 표현.

- 간단하게는 데이터 컬렉션 반복을 처리하는 기능.

- 스트림을 이용하면 멀티 스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬처리 가능.

- 스트림의 소프트웨어공학적으로 아래와 같은 다양한 이득을 제공.

> 선언형으로 코드 구현 가능.

* 루프와 if 조건문 등의 제어 블록을 사용하지 않고도 같은 동작의 수행 지정 가능.

* 선언형 코드와 동작 파라미터화를 활용하면 변화하는 요구사항에 쉽게 대응.

> filter, sorted, map, collect같은 여러 빌딩 블록 연산을 연결해서 복잡한 데이터 처리 파이프라인 생성 가능.

> 여러개의 파이프라인으로 연결되도 가독성과 명확성이 유지.

- 스트림은 filter(또는 sorted, map, collect) 같은 연산은 고수준 빌딩 블록으로 이루어져 있음.

- 특정 스레딩 모델에 제한되지 않고 자유롭게 어떤 상황에서든 사용 가능.

- 결과적으로 데이터 처리 과정을 병렬화하면서 스레드와 락을 걱정할 필요 없음.

- 스트림 API의 특징

> 선언형

* 더 간결하고 가독성 향상.

> 조립 가능

* 유연성 향상.

> 병렬화

* 성능 향상.


2. 스트림 시작하기

- 컬렉션은 스트림 작업 중 가장 간단한 작업 중 하나.

- 자바 8의 컬렉션에서는 스트림을 반환하는 stream 메서드 추가.

> 스트림 인터페이스 정의 : java.util.stream.Stream 참고

- 스트림이란?

> "데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소"로 정의 가능.


> 연속된 요소

* 컬렉션과 마찬가지로 스트림은 특정 요소 형식으로 이루어진 연속된 값 집합의 인터페이스 제공

* 컬렉션은 자료구조로 시간과 공간의 복장성과 관련된 요소 저장 및 접근 연산이 주를 이룸.

* 스트림은 filter, sorted, map처럼 표현 계산식이 주를 이룸.

* 즉, 컬렉션의 주제는 데이터고 스트림의 주제는 계산.


> 소스

* 스트림은 컬렉션, 배열, I/O 자원등의 데이터 제공 소스로부터 데이터를 소비.


> 데이터 처리 연산

* 함수형 프로그래밍 언어에서 일반적으로 지원하는 연산과 데이터베이스와 비슷한 연산 지원.

* filter, map, reduce, find, match, sort등 데이터 조작 가능.


> 파이프라이닝

* 스트림 연산끼리 연결해 커다란 파이프라인을 구성할 수 있도록 스트림 반환.

* 그로 인해 게으름(laziness), 쇼티서킷(short-circuiting:단선) 같은 최적화도 얻음.

* 연산 파이프라인은 데이터베이스 질의와 비슷.


> 내부 반복

* 반복자를 이용하여 명시적 반복 -> 외부 반복

* 반복을 알아서 처리하고 결과 스트림 값을 어딘가에 저장 -> 내부 반복

- 예제에 사용될 데이터

- 예제

> filter

* 람다를 인수로 받아 스트림에서 특정 요소를 제외.


> map

* 람다를 이용해 한 요소를 변환하거나 정보를 추출.


> limit

* 정해진 개수 이상의 요소가 스트림에 저장되지 못하게 스트림의 크기 축소.


> collect

* 스트림을 다른 형식으로 변환.

* 다양한 변환 방법을 인수로 받아 스트림에 누적된 요소를 특정 결과로 변환.


3. 스트림과 컬렉션

- 컬렉션과 스트림 모두 연속된 요소 형식의 값을 저장하는 자료구조 인터페이스를 제공.

- 연속된(Sequenced)이라는 표현은 순서와 상관없이 아무 값이나 접속 하는 것이 아니라 순차적으로 접근한다는 의미.


- 컬렉션과 스트림의 차이

> ex) 영화 DVD - 컬렉션, 인터넷 스트리밍 - 스트림

> 데이터를 "언제" 계산하느냐가 가장 큰 차이점.

> 컬렉션은 현재 자료구조가 포함하는 "모든"값을 메모리에 저장하는 구조

* 즉, 모든 요소는 컬렉션에 추가하기 전에 계산되어야 함.

> 스트림은 이론적으로 "요청할 때만 요소를 계산"하는 고정된 자료구조.

* 사용자가 요청한 값만 스트림에서 추출한다는 것이 핵심.

> 스트림은 생산자와 소비자 관계를 형성.

> 스트림은 게으르게 만들어지는 컬렉션과 같음.

* 즉, 사용자가 데이터를 요청할 때만 값을 계산.

> 반면, 컬렉션은 적극적으로 생성.

* 무제한의 소수를 추출할 경우 컬렉션은 모든 소수를 포함하려 할 것이므로 무한루프를 돌면서 새로운 소수를 계산 및 추가하기를 반복.

* 결국 영원히 결과를 볼 수 없게됨.

> 스트림의 한 예로 구글 검색이 있음.


3.1 딱 한번만 검색 가능.

- 반복자와 마찬가지로 스트림도 한 번만 탐색 가능.

> 즉, 탐색된 스트림의 요소는 소비.

- 반복자와 마찬가지로 한 번 탐색한 요소를 다시 탐색하기 위해서는 초기 데이터 소스에서 새로운 스트림을 얻어야 함.


3.2 외부 반복과 내부 반복

- 컬렉션 인터페이스를 사용하려면 사용자가 직접 요소를 반복해야 함.

> 이를 외부 반복이라 함.

- 스트림 라이브러리는 내부 반복을 사용함.

> 반복을 알아서 처리하고 결과 스트림값을 어딘가에 저장해줌.

> 함수에 어떤 작업을 수행하지만 지정하면 모든 것이 알아서 처리 됨.

- 내부 반복이 좋은 2가지 이유

> 작업을 투명하게 병렬 처리 가능.

> 더 최적화된 다양한 순서로 처리 가능.

- 자바8에서 스트림을 제공하는 이유

> 스트림 라이브러리의 내부 반복은 데이터 표현과 하드웨어를 활용하는 병렬성 구현을 자동으로 선택.

* 외부 반복에서는 병렬성을 스스로 관리해야함(스스로 관리한다는 것은 병렬성을 포기 or synchronized 처리)


4. 스트림 연산

- java.util.stream.Stream 인터페이스는 많은 연산을 정의.

- 스트림 인터페이스의 연산은 크게 2가지로 구분 가능.

> 연결 가능 스트림 연산 - 중간연산

> 스트림을 닫는 연산 - 최종연산


4.1 중간 연산

- filter나 sorted 같은 중간 연산은 다른 스트림을 반환.

- 여러 중간 연산을 연결해서 질의를 만들수 있음.

- 단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 수행하지 않음.

> 즉, 게으르다는 것(lazy)

- 중간 연산을 합친 다음에 합쳐진 중간 연산을 최종 연산으로 한번에 처리.

- 스트림의 게으른 특성 덕분에 몇 가지 최적화 효과 획득.

- 중간연산 예제 결과

filtering: pork

mapping: pork

filtering: beef

mapping: beef

filtering: chicken

mapping: chicken

중간연산 테스트: [pork, beef, chicken]


4.2 최종 연산

- 스트림 파이프라인에서 결과 도출.

- 최종 연산에 의해 List, Integer, void등 스트림 이외의 결과 반환.


4.3 스트림 이용하기

- 스트림 이용 과정은 다음과 같이 세 가지로 요약 가능.

> 질의를 수행할 (컬렉션 같은) 데이터 소스

> 스트림 파이프라인을 구성할 중간 연산 연결

> 스트림 파이프라인을 실행하고 결과를 만들 최종 연산.


- 스트림 파이프라인의 개념은 빌더 패턴(builder pattern)과 비슷.

> 빌더 패턴은 호출을 연결해서 설정을 만듦 <-> 스트림에서 중간 연산을 연결

> 준비된 설정에 build 메서드 호출 <-> 스트림에서 최종 연산.


표. 중간연산

연산

형식

반환형식

연산의 인수

함수 디스크립터

filter

중간 연산

Stream<T>

Predicate<T>

T -> boolean

map

중간 연산

Stream<T>

Function<T, R>

T -> R

limit

중간 연산

Stream<T>



sotred

중간 연산

Stream<T>

Comparator<T>

(T, T) -> int

distinct

중간 연산

Stream<T>



표. 최종연산

연산

형식

목적

forEach

최종 연산

스트림의 요소를 소비하면서 람다 적용. void 반환

count

최종 연산

스트림의 요소 개수를 반환. long 반환

collect

최종 연산

스트림을 리듀스해서 리스트, , 정수 형식의 컬렉션 생성.


5. 요약

- 스트림은 소스에서 추출된 연속 요소로, 데이터 처리 연산을 지원.

- 스트림은 내부 반복을 지원.

> 내부 반복은 filter, map, sorted 등의 연산으로 반복을 추상화.

- 중간 연산과 최종 연산이 있음.

> 중간연산: filter와 map처럼 스트림을 반환하면서 다른 연산과 연결될 수 있는 연산.

> 최종연산: forEach나 count처럼 스트림 파이프라인을 처리해서 스트림이 아닌 결과를 반환하는 연산.

- 스트림의 요소는 요청할 때만 계산.



'Java8 > Java 8 in Action' 카테고리의 다른 글

스트림 활용 예제 소스  (0) 2017.06.26
스트림 활용  (0) 2017.06.26
람다 표현식  (0) 2017.06.15
동작 파라미터화 예제 소스  (0) 2017.06.13
자바8 동작 파라미터화 코드 전달하기  (0) 2017.06.13

+ Recent posts