스트림 활용 에제 Model
- Transaction
'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 |
스트림 활용 에제 Model
- Transaction
병렬 데이터 처리와 성능 (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 같은 메서드로도 스트림 생성 가능.
- 크기가 정해지지 않은 스트림을 무한 스트림이라 함.
스트림으로 데이터 수집 (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처럼 스트림 파이프라인을 처리해서 스트림이 아닌 결과를 반환하는 연산.
- 스트림의 요소는 요청할 때만 계산.
스트림 활용 예제 소스 (0) | 2017.06.26 |
---|---|
스트림 활용 (0) | 2017.06.26 |
람다 표현식 (0) | 2017.06.15 |
동작 파라미터화 예제 소스 (0) | 2017.06.13 |
자바8 동작 파라미터화 코드 전달하기 (0) | 2017.06.13 |