스트림 활용 에제 Model

- Transaction


- Trader

- 예제 소스

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

병렬 데이터 처리와 성능  (0) 2017.06.29
스트림으로 데이터 수집  (0) 2017.06.29
스트림 활용  (0) 2017.06.26
스트림 소개  (0) 2017.06.16
람다 표현식  (0) 2017.06.15

5. 스트림 활용

데이터를 어떻게 처리할지는 스트림 API가 관리.

편리하게 데이터 관련 작업 가능.

스트림 API 내부적으로 다양한 최적화가 이루어질 수 있음.

스트림 API는 내부 반복 뿐 아니라 코드를 병렬로 실행할지 여부도 결정 가능.

스트림 API가 지원하는 연산을 이용하여 필터링, 슬라이싱, 매핑, 검색, 매칭, 리듀싱 등 다양한 데이터 처리 질의 표현 가능.


1. 필터링과 슬라이싱

1.1 프레디케이트로 필터링

- filter 메서드는 프레디케이드(boolean을 반환하는 함수)를 인수로 받아 프레디케이트와 일치하는 모든 요소를 포함하는 스트림 반환.


1.2 고유 요소 필터링

- 고유 요소로 이루어진 스트림을 반환하는 distinct 메서드 지원.

> 고유 여부는 스트림에서 만든 객체의 hashCode, equals로 결정됨.


1.3 스트림 축소

- 주어진 사이즈 이하의 크기를 갖는 새로운 스트림을 반환하는 limit 메서드 지원.

- 스트림이 정렬되어 있으면 최대 n개의 요소 반환 가능.


1.4 요소 건너뛰기

- 처음 n개의 요소를 제외한 스트림을 반환하는 skip 메서드 지원.

- limit과 skip은 상호 보완적 연산을 수행.


2. 매핑

- 특정 객체에서 특정 데이터를 선택하는 작업.

- 스트림 API의 map과 flatMap 메서드는 특정 데이터를 선택하는 기능 제공.


2.1 스트림의 각 요소에 함수 적용

- 스트림은 함수를 인수로 받는 map 메서드 지원.

- 인수로 제공된 함수는 각 요소에 적용. 함수를 적용한 결과가 새로운 요소로 매핑.

> 이 과정은 기존의 값을 "고친다 modify"라는 개념 보다 "새로운 버전 생성"이라는 개념에 가까움.

> "변환 transforming"에 가까운 "매핑 mapping"이라는 단어 사용.


2.2 스트림 평면화

- 스트림의 각 값을 다른 스트림으로 만든 다음 모든 스트림을 하나의 스트림으로 연결하는 메서드를 flatMap 메서드라 함.

- flatMap은 객 배열을 스트림이 아니라 스트림의 콘텐츠로 매핑함.

> 즉, map과 달리 faltMap은 하나의 평면화된 스트림을 반환.


3. 검색과 매칭

- 특정 속성이 데이터 집합에 있는지 여부를 검색

- allMatch, anyMatch, noneMatch, findFirst, findAny등 다양한 유틸리티 메서드 제공.


3.1 프레디케이트가 적어도 한 요소와 일치하는지 확인

- 프레디케이트가 주어진 스트림에 적어도 한 요소와 일치하는지 확인할 때 anyMatch 메서드 이용.

- anyMatch는 boolean 반환하는 최종 연산.


3.2 프레디케이트가 모든 요소와 일치하는지 확인

- 프레디케이트가 주어진 스트림의 모든 요소와 일치하는지 확인 할 때 allMatch 메서드 이용.

- 프레디케이드가 주어진 스트림의 일치하는 요소가 없는 확인 할 때 noneMatch 메서드 이용. (allMatch 와 반대 연산)

- anyMatch, allMatch, noneMatch 세 가지 메서드는 스트림 쇼트서킷 기법, 즉 자바의 &&, ||와 같은 연산.


쇼트서킷이란?

- 여러 and 연산으로 연결된 커다란 boolean 표현식을 평가한다고 가정할 때, 표현식에서 하나라도 거짓이라는 결과가 나오면 나머지 표현식의 결과와 상관없이 전체 결과도 거짓이 됨.

> 이러한 상황을 쇼트서킷이라 부름.


3.3 요소 검색

- 현재 스트림에서 임의 요소 반환하는 매서드는 findAny.

- findAny 메서드를 다른 스트렘 연산과 연결해서 사용 가능.


Optional이란?

- 값의 존재나 부재 여부를 표현하는 컨테이너 클래스.

- fnidAny는 아무 요소도 반환하지 않을 수 있음.

- null은 쉽게 에러를 발생 시킬 수 있으므로 자바 8 라이브러리 설계자는 Optional<T> 기능 구현.


1) isPresent() - Optional이 값을 포함하는 true 반환. 포함하지 않으면 false 반환.

2) ifPresnet(Consumer<T> block) - 값이 있으면 주어진 블록 실행. T 형식의 인수를 받으며 void를 반환하는 람다 전달 가능.

3) T get() - 값이 존재하면 값 반환. 값이 없으면 NoSuchElementException 발생.

4) T orElse(T Other) - 값이 있으면 값 반환. 값이 없으면 기본값 반환.


3.4 첫번째 요소 찾기

- 리스트 또는 정렬된 연속 데이터로부터 생성된 스트림처럼 일부 스트림에는 논리적 아이템 순서가 정해져있을 수 있음.

- 이런 스트림의 첫 번재 요소를 찾는 findFirst 메서드 제공.


4. 리듀싱

- 모든 스트림 요소를 처리해 값으로 도출하는 질의를 리듀싱 연산이라 함.

- 함수형 프로그래밍 언어 용어로는 이 과정이 마치 종이를 작은 조각이 될 때까지 반복해서 접는 것과 비슷하다는 의미로 폴드(fold)라 부름.


표 5-1 작성.


5. 실전 예제

- 추가 포스팅에서 예제 제공.


6. 숫자형 스트림

- 숫자 스트림을 효율적으로 처리할 수 있도록 스트림 API는 기본형 특화 스트림 제공.


6.1 기본형 특화 스트림

- 박싱 비용을 피할 수 있도록 'int', 'double', 'long' 요소에 특화된 스트림 제공

> IntStream, DoubleStream, LongStream

- 각각의 인터페이스는 숫자 스트림의 합계를 계산할 수 있는 sum, 최댓값 요소를 검색하는 max등 자주 사용하는 숫자 관련 리듀싱 연산 수행 메서드 제공.

- 필요할 때 다시 객체 스트림으로 복원하는 기능도 제공.

- 특화 스트림은 오직 박싱과정에서 일어나는 효율성과 관련 있으며 스트림에 추가 기능을 제공하지 않음.

- boxed()를 통해 특화 스트림을 일반 스트림으로 변환 가능.

- Optional -> OptionalInt, OptionalDouble, OpionalLong 특화 스트림 버전 제공.


6.2 숫자 범위

- IntStream, LongStream에 range와 rangeClosed 두 가지 정적 메서드 제공.


7. 스트림 만들기

- stream 메서드로 컬렉션에서 스트림 획득 가능.


7.1 값으로 스트림 만들기

- 임의의 수를 인수로 받는 정적 메서드 Stream.of를 이용해 스트림 생성 가능.


7.2 배열로 스트림 만들기

- 배열을 인수로 받는 정적 메서드 Arrays.stream을 이용해 스트림 생성 가능.


7.3 파일로 스트림 만들기

- java.nio.file.Files의 많은 정적 메서드가 스트림을 반환.


7.4 함수로 무한 스트림 만들기

- 함수에서 스트림을 만들 수 있는 두 개의 정적 메서드 Stream.iterate와 Stream.generate 제공.

- iterate와 generate에서 만든 스트림은 요청할 때 마다 주어진 함수를 이용해 값을 생성.

> 따라서 무제한으로 값 계산 가능.

- iterate

> 초깃값과 람다를 신수로 받아 새로운 값을 끊임없이 생산 가능.

> 요청할 떄 마다 값을 생산 할 수 있으며 무한 스트림 생성 가능.

* 이러한 스트림을 언바운드 스트림이라 표현.

> 일반적으로 연속된 일련의 값을 만들때 iterate 사용.

- generate

> 요구할 떄 값을 계산하는 무한 스트림 생성 가능.

> iterate와 달리 생산된 각 값을 연속적으로 계산하지 않음.

> Supplier<T>를 인수로 받아 새로운 값을 생산.


8. 요약

- 복잡한 데이터 처리 질의 표현 가능.

- filter, distinct, skip, limit 메서드로 스트림을 필터링 하거나 자르기 가능.

- map, flatMap 메서드로 스트림 요소를 추출하거나 변환 가능.

- findFirst, findAny 메서드로 스트림의 요소 검색 가능.

- allMatch, noneMatcj, anyMatch 메서드를 이용해서 주어진 프레디케이트와 일치하는 요소를 스트림에서 검색 가능.

> 이들 메서드를 쇼트서킷, 즉 결과를 찾는 즉시 반환, 전체 스트림을 처리하지 않음.

- reduce 메서드로 스트림의 모든 요소를 반복 조합하며 값 도출 가능.

- filter, map등은 상태를 지정하지 않는 상태없는 연산.

- reduce 같은 연산은 계산하는데 필요한 상태 저장.

- sorted, distinct 등의 메서드는 새로운 스트림을 반환하기에 앞서 스트림의 모든 요소를 버퍼에 저장.

> 이런 메서드를 상태 있는 연산이라 부름.

- IntStream, DoubleStram, LongStream은 기본형 특화 스트림.

> 이들 연산은 기본형에 맞게 특화.

- 컬렉션뿐 아니라 값, 배열, 파일, iterate, generate 같은 메서드로도 스트림 생성 가능.

- 크기가 정해지지 않은 스트림을 무한 스트림이라 함.

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

스트림으로 데이터 수집  (0) 2017.06.29
스트림 활용 예제 소스  (0) 2017.06.26
스트림 소개  (0) 2017.06.16
람다 표현식  (0) 2017.06.15
동작 파라미터화 예제 소스  (0) 2017.06.13

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