Stream API
자바카페
김흥래
스트림
• 컬렉션은 자바에서 가장 많이 사용하는 기
능이다. (Collections Framework)
• 대부분의 어플리케이션은 컬렉션을 만들
고 컬렉션 데이터를 처리하는 과정을 반복
한다.
• Java8에서는 컬렉션 데이터를 처리하기 위
한 Stream API가 추가되었다.
스트림
• 스트림은 컬렉션 데이터를 처리하는 코드를 선언형으로 구현
할 수 있다.
• 제어문(for, while, if)을 이용하여 컬렉션 데이터가 어떻게 동
작을 해야 하는지 일일이 코딩 할 필요없이 ‘어떤 동작을 수행
하라’는 형태의 선언만으로 컬렉션 데이터를 가공할 수 있다.
• SQL과 비슷한 형태이다.
– Select * from user where age > 10;
– 위와 같이 단순한 선언문만으로 디스크의 정보를 가공하여 가져
올 수 있다.
– 프로그래머가 직업 제어문을 이용하여 row나 column 데이터를
어떻게 가공할 지 코딩하지 않는다.
– 연산 과정은 전적으로 DB Optimizer에게 위임하고 결과에만 관
심이 있다.
스트림
• Stream API는 filter, sort, map, collect 같은 빌딩 블록 연산을
제공한다.
• 여러 블록 연산을 연결하여 파이프라인을 구현하는 것이 가능
하다. (Divide & Conquer)
• 고수준의 블록 연산을 제공하여 특정 스레드 모델에 제한되지
않고 멀티코어 아키텍처를 투명하게 활용할 수 있다.
• 병렬처리시 스레드와 락을 고민하지 않아도 된다.
• 스트림이란 데이터 처리 연산을 지원하도록 소스에서 추출된
연속된 요소
스트림 vs 컬렉션
• 컬렉션과 스트림 모두 연속된 요소 형식의 값을 저
장하는 자료구조의 인터페이스를 제공한다.
• 둘다 순서에 따라 순차적으로 요소에 접근한다.
• 컬렉션과 스트림의 가장 큰 차이는 데이터를 언제
계산하느냐 이다.
– 컬렉션 : 요소를 접근하면서 계산식을 만날때마다 계
산이 일어난다.
– 스트림 : 최종연산을 요청할 때만 계산한다.
데이터 접근 측면
• 컬렉션
– 자료구조이므로 데이터에 접근, 읽기, 변경, 저장 같
은 연산이 주요 관심사이다. (직접 데이터 핸들링)
– 데이터에 접근하는 방법을 직접 작성해야 한다.
• 스트림
– Filter, sorted, map 처럼 계산식(람다)을 표현하는
것이 주요 관심사이다. (계산식을 JVM으로 던진다.)
– 데이터에 접근하는 방법이 추상화 되어 있다.
데이터 계산 측면
• 컬렉션
– 작업을 위해서 Iterator로 모든 요소를 순환해야 한다.
– 메모리에 모든 요소가 올라간다. (OOM)
– 모든 요소가 메모리에 올라가 있는 상태에서 요소를 누적
시키며 결과를 계산한다.
– 메모리 사용량 연산속도
• 스트림
– 계산식(알고리즘 or 람다)을 미리 적어두고 계산시에 람다
로 JVM에 넘긴다.
– 내부에서 요소를 어떻게 메모리에 올리는 지는 관심사가
아니다. (블랙박스)
– 계산 요청시 결과가 바로 리턴된다.
– 메모리 사용량 연산속도
스트림 연산
• 스트림은 2가지로 연산이 구분된다.
– Filter, map, limit는 서로 연결되어 파이프라인
을 형성한다.
– Collect로 마지막 파이프라인을 수행후 완료한
다.
• 중간연산 (Intermediate Operation)
• 최종연산 (Terminal Operation)
filter map limit collect
중간연산 최종연산
Menu.stream().filter(d -> d.getCalories() > 300)
.map(Dish::getName)
.limit(3)
.collect(toList());
중간연산은 파이프라인으로 연결되어
선언식이 최종연산으로 전달된다.
중간연산 정보를 스트림으로 입력받
아 최종연산에서 한번에 처리한다.
단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 수행하지 않는다. (Lazy)
모든 중간연산을 합친 다음 최종연산에서 한번에 처리한다.
필터링과 슬라이싱
• 스트림의 요소를 선택하는 방법을 블록 연산
으로 제공
• 주요 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);
매핑
• 스트림의 특정 객체에서 원하는 데이터만
선택하는 작업을 블록 연산으로 제공
• 주요 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);
검색과 매칭
• 특정 속성이 데이터 집합에 존재하는지 여부를 검색하
는 기능을 블록 연산으로 제공
• 주요 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();
리듀싱
• 스트림의 요소를 누적하여 어떠한 연산을
하는 기능을 블록 연산으로 제공
• 주요 API
// 초기값 T를 기준으로 연산을 누적시켜 결과를 제공
T reduce(T identity, BinaryOperator<T> accumulator);
// 초기값 없이 연산을 누적시켜 Optional로 리턴 (Max, Min)
Optional<T> reduce(BinaryOperator<T> accumulator);
long count();
Quiz
기본형 특화 스트림
• Java8에서 3가지 기본형(Primitive) 특화 스트림을 제공한다.
– IntStream
– DoubleStream
– LongStream
• 기존 스트림을 특화 스트림으로 변화
IntStream mapToInt(ToIntFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
• 특화 스트림을 객체 스트림으로 복원
Stream<Integer> boxed();
Stream<Double> boxed();
Stream<Long> boxed();
• 특정범위의 숫자를 스트림으로 생성
// 시작값과 종료값 미포함
public static IntStream range(int startInclusive, int endExclusive) {}
// 시작값과 종료값 포함
public static IntStream rangeClosed(int startInclusive, int endInclusive) {}
AutoBoxing 비용
캐스팅 비용이 겁나 비싸다!!!
스트림 만들기
• 스트림을 일반적으로 컬렉션에서 생성하지만 직접 스트림을
생성하는 것도 가능하다.
• 주요 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) {}
스트림 만들기
• 무한스트림 생성시 주의점
– 성능을 고려하여 항상 limit() 연산자와 함께 사용해야 한다.
– Generate를 사용할 경우 병렬코드에 주의해야 한다.
• Stream.iterator
– 단항연산자 타입을 인수로 받는다.
– 기준값을 가지고 있으므로 내부에서 누적시키면서 연산하
므로 병렬 코드에 안전하다.
• Stream.generate
– 공급자 타입을 인수로 받는다.
– 특정 객체가 누적 연산을 하도록 위임하므로 병렬 코드에
안전 여부를 반드시 확인해야 한다.
(공급된 객체가 반드시 불변객체여야 한다.)
3. stream api

3. stream api

  • 1.
  • 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) • 고수준의 블록 연산을 제공하여 특정 스레드 모델에 제한되지 않고 멀티코어 아키텍처를 투명하게 활용할 수 있다. • 병렬처리시 스레드와 락을 고민하지 않아도 된다. • 스트림이란 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소
  • 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 limitcollect 중간연산 최종연산 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);
  • 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);
  • 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();
  • 24.
    리듀싱 • 스트림의 요소를누적하여 어떠한 연산을 하는 기능을 블록 연산으로 제공 • 주요 API // 초기값 T를 기준으로 연산을 누적시켜 결과를 제공 T reduce(T identity, BinaryOperator<T> accumulator); // 초기값 없이 연산을 누적시켜 Optional로 리턴 (Max, Min) Optional<T> reduce(BinaryOperator<T> accumulator); long count();
  • 26.
  • 41.
    기본형 특화 스트림 •Java8에서 3가지 기본형(Primitive) 특화 스트림을 제공한다. – IntStream – DoubleStream – LongStream • 기존 스트림을 특화 스트림으로 변화 IntStream mapToInt(ToIntFunction<? super T> mapper); DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper); LongStream mapToLong(ToLongFunction<? super T> mapper); • 특화 스트림을 객체 스트림으로 복원 Stream<Integer> boxed(); Stream<Double> boxed(); Stream<Long> boxed(); • 특정범위의 숫자를 스트림으로 생성 // 시작값과 종료값 미포함 public static IntStream range(int startInclusive, int endExclusive) {} // 시작값과 종료값 포함 public static IntStream rangeClosed(int startInclusive, int endInclusive) {}
  • 42.
  • 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) {}
  • 47.
    스트림 만들기 • 무한스트림생성시 주의점 – 성능을 고려하여 항상 limit() 연산자와 함께 사용해야 한다. – Generate를 사용할 경우 병렬코드에 주의해야 한다. • Stream.iterator – 단항연산자 타입을 인수로 받는다. – 기준값을 가지고 있으므로 내부에서 누적시키면서 연산하 므로 병렬 코드에 안전하다. • Stream.generate – 공급자 타입을 인수로 받는다. – 특정 객체가 누적 연산을 하도록 위임하므로 병렬 코드에 안전 여부를 반드시 확인해야 한다. (공급된 객체가 반드시 불변객체여야 한다.)