2. 스트림
• 컬렉션은 자바에서 가장 많이 사용하는 기
능이다. (Collections Framework)
• 대부분의 어플리케이션은 컬렉션을 만들
고 컬렉션 데이터를 처리하는 과정을 반복
한다.
• Java8에서는 컬렉션 데이터를 처리하기 위
한 Stream API가 추가되었다.
3. 스트림
• 스트림은 컬렉션 데이터를 처리하는 코드를 선언형으로 구현
할 수 있다.
• 제어문(for, while, if)을 이용하여 컬렉션 데이터가 어떻게 동
작을 해야 하는지 일일이 코딩 할 필요없이 ‘어떤 동작을 수행
하라’는 형태의 선언만으로 컬렉션 데이터를 가공할 수 있다.
• SQL과 비슷한 형태이다.
– Select * from user where age > 10;
– 위와 같이 단순한 선언문만으로 디스크의 정보를 가공하여 가져
올 수 있다.
– 프로그래머가 직업 제어문을 이용하여 row나 column 데이터를
어떻게 가공할 지 코딩하지 않는다.
– 연산 과정은 전적으로 DB Optimizer에게 위임하고 결과에만 관
심이 있다.
4. 스트림
• Stream API는 filter, sort, map, collect 같은 빌딩 블록 연산을
제공한다.
• 여러 블록 연산을 연결하여 파이프라인을 구현하는 것이 가능
하다. (Divide & Conquer)
• 고수준의 블록 연산을 제공하여 특정 스레드 모델에 제한되지
않고 멀티코어 아키텍처를 투명하게 활용할 수 있다.
• 병렬처리시 스레드와 락을 고민하지 않아도 된다.
• 스트림이란 데이터 처리 연산을 지원하도록 소스에서 추출된
연속된 요소
5.
6. 스트림 vs 컬렉션
• 컬렉션과 스트림 모두 연속된 요소 형식의 값을 저
장하는 자료구조의 인터페이스를 제공한다.
• 둘다 순서에 따라 순차적으로 요소에 접근한다.
• 컬렉션과 스트림의 가장 큰 차이는 데이터를 언제
계산하느냐 이다.
– 컬렉션 : 요소를 접근하면서 계산식을 만날때마다 계
산이 일어난다.
– 스트림 : 최종연산을 요청할 때만 계산한다.
7. 데이터 접근 측면
• 컬렉션
– 자료구조이므로 데이터에 접근, 읽기, 변경, 저장 같
은 연산이 주요 관심사이다. (직접 데이터 핸들링)
– 데이터에 접근하는 방법을 직접 작성해야 한다.
• 스트림
– Filter, sorted, map 처럼 계산식(람다)을 표현하는
것이 주요 관심사이다. (계산식을 JVM으로 던진다.)
– 데이터에 접근하는 방법이 추상화 되어 있다.
8. 데이터 계산 측면
• 컬렉션
– 작업을 위해서 Iterator로 모든 요소를 순환해야 한다.
– 메모리에 모든 요소가 올라간다. (OOM)
– 모든 요소가 메모리에 올라가 있는 상태에서 요소를 누적
시키며 결과를 계산한다.
– 메모리 사용량 연산속도
• 스트림
– 계산식(알고리즘 or 람다)을 미리 적어두고 계산시에 람다
로 JVM에 넘긴다.
– 내부에서 요소를 어떻게 메모리에 올리는 지는 관심사가
아니다. (블랙박스)
– 계산 요청시 결과가 바로 리턴된다.
– 메모리 사용량 연산속도
9. 스트림 연산
• 스트림은 2가지로 연산이 구분된다.
– Filter, map, limit는 서로 연결되어 파이프라인
을 형성한다.
– Collect로 마지막 파이프라인을 수행후 완료한
다.
• 중간연산 (Intermediate Operation)
• 최종연산 (Terminal Operation)
10. filter map limit collect
중간연산 최종연산
Menu.stream().filter(d -> d.getCalories() > 300)
.map(Dish::getName)
.limit(3)
.collect(toList());
중간연산은 파이프라인으로 연결되어
선언식이 최종연산으로 전달된다.
중간연산 정보를 스트림으로 입력받
아 최종연산에서 한번에 처리한다.
단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 수행하지 않는다. (Lazy)
모든 중간연산을 합친 다음 최종연산에서 한번에 처리한다.
11. 필터링과 슬라이싱
• 스트림의 요소를 선택하는 방법을 블록 연산
으로 제공
• 주요 API
// Predicate와 일치하는 데이터만 Stream으로 반환
Stream<T> filter(Predicate<? super T> predicate);
// Stream 데이터 중복제거 (hashcode & equals)
Stream<T> distinct();
// Stream 데이터 축소 (Cut)
Stream<T> limit(long maxSize);
// Stream 데이터 건너뛰기
Stream<T> skip(long n);
12.
13.
14.
15. 매핑
• 스트림의 특정 객체에서 원하는 데이터만
선택하는 작업을 블록 연산으로 제공
• 주요 API
// 데이터의 람다식 결과가 새로운 Stream으로 제공
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
// 람다식 결과가 새로운 Stream으로 제공되는 것은 동일
// 만약 결과가 여러개의 스트림(Stream<Object[]>)일때
하나의 스트림으로 변환
<R> Stream<R>
flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
16.
17.
18.
19.
20. 검색과 매칭
• 특정 속성이 데이터 집합에 존재하는지 여부를 검색하
는 기능을 블록 연산으로 제공
• 주요 API
// Predicate가 하나라도 일치하는지 검사
boolean anyMatch(Predicate<? super T> predicate);
// Predicate가 모두 일치하는지 검사
boolean allMatch(Predicate<? super T> predicate);
// Predicate가 모두 불일치하는지 검사
boolean noneMatch(Predicate<? super T> predicate);
// Stream에서 아무 데이터나 Optional로 리턴
Optional<T> findAny();
// Stream에서 첫번째 데이터를 Optional로 리턴 (병렬 실행시 성능이슈)
Optional<T> findFirst();
21.
22.
23.
24. 리듀싱
• 스트림의 요소를 누적하여 어떠한 연산을
하는 기능을 블록 연산으로 제공
• 주요 API
// 초기값 T를 기준으로 연산을 누적시켜 결과를 제공
T reduce(T identity, BinaryOperator<T> accumulator);
// 초기값 없이 연산을 누적시켜 Optional로 리턴 (Max, Min)
Optional<T> reduce(BinaryOperator<T> accumulator);
long count();
45. 스트림 만들기
• 스트림을 일반적으로 컬렉션에서 생성하지만 직접 스트림을
생성하는 것도 가능하다.
• 주요 API
// 문자열로 스트림 생성
public static<T> Stream<T> of(T t) {}
// 배열로 스트림 생성
public static <T> Stream<T> stream(T[] array) {}
// 무한스트림 생성 (초기값이 존재, 내부에서 연산이 누적된다.)
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {}
// 무한스트림 생성 (초기값이 미존재, 외부에서 연산을 가져온다.)
public static<T> Stream<T> generate(Supplier<T> s) {}
46.
47. 스트림 만들기
• 무한스트림 생성시 주의점
– 성능을 고려하여 항상 limit() 연산자와 함께 사용해야 한다.
– Generate를 사용할 경우 병렬코드에 주의해야 한다.
• Stream.iterator
– 단항연산자 타입을 인수로 받는다.
– 기준값을 가지고 있으므로 내부에서 누적시키면서 연산하
므로 병렬 코드에 안전하다.
• Stream.generate
– 공급자 타입을 인수로 받는다.
– 특정 객체가 누적 연산을 하도록 위임하므로 병렬 코드에
안전 여부를 반드시 확인해야 한다.
(공급된 객체가 반드시 불변객체여야 한다.)