스트림 API가 지원하는 다양한 연산들을 살펴보자.
Stream<T> filter(Predicate<? super T> predicate);
intStream.filter(i -> i%2 != 0 && i%3 != 0)...
intStream.filter(i -> i%2 != 0).filter(i%3 != 0)...
hashCode
, equals
로 결정된다. Stream<T> distinct();
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
playerStream.sorted(Comparator.comparing(Player::getTeam)
.thenComparing(Player::getScore)
.thenComparing(Player::getName))
.forEach(System.out::println);
자바9에서 추가된 메서드인 takeWhile(), dropWhile()
true인 동안 요소
를 가져와 스트림을 슬라이스 할 수 있다. default Stream<T> takeWhile(Predicate<? super T> predicate)
List<Food> slicedMenu = calorieSortedMenu.stream()
.takeWhile(dish -> dish.getCalories() < 300)
.collect(toList());
남은 모든 요소
를 반환한다. default Stream<T> dropWhile(Predicate<? super T> predicate)
List<Food> slicedMenu = calorieSortedMenu.stream()
.dropWhile(dish -> dish.getCalories() < 300)
.collect(toList());
Stream<T> limit(long maxSize);
Stream<T> skip(long n);
특정 객체에서 특정 데이터를 선택하는 처리 과정에서 자주 수행되는 연산
새로운 요소
로 매핑된 스트림을 반환한다. <R> Stream<R> map(Function<? super T, ? extends R> mapper);
List<Integer> wordLengths = Arrays.asList("one", "two", "three").stream()
.map(String::length)
.collect(toList());
스트림의 요소가 배열기나 map()의 연산결과가 배열인 경우, 즉 스트림의 타입이 Stream<T[]>
인 경우 Stream<T>
로 다루고 싶거나 Stream가 더 편리할 때 사용하는 메서드
스트림의 각 값을 다른 스트결과적으로 하나의 평면화된 스트림을 반환한다.
String[][] wordArr = new String[][]{
new String[]{"team", "victory", "fighting"},
new String[]{"flatMap", "is", "too", "difficult"}
};
Arrays.stream(wordArr) // Stream<String[]>
.map(innerArray -> Arrays.stream(innerArray)) // Stream<Stream<String>>
.forEach(innerStream -> innerStream.filter(word -> word.length() > 5) // Stream<String>
.forEach(System.out::println));
Arrays.stream(wordArr) // Stream<String[]>
.flatMap(innerArray -> Arrays.stream(innerArray)) // // Stream<String>
.filter(word -> word.length() > 5)
.forEach(System.out::println);
List<String> kinds = Arrays.asList("스페이드", "하트", "다이아몬드", "클로버");
List<String> nums = Arrays.asList("A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K");
List<String[]> cards = kinds.stream()
.flatMap(kind -> nums.stream()
.map(num -> new String[] {kind, num}))
.peek(arr -> System.out.println(Arrays.toString(arr)))
.collect(Collectors.toList());
특정 속성이 데이터 집합에 있는지 여부를 검색하는 데이터 처리 유틸리티 메서드를 제공한다.
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
Predicate
와 일치하지 않으면 trueboolean noneMatch(Predicate<? super T> predicate);
anyMatch, allMatch, noneMatch 세 메서드는 스트림 쇼트서킷 기법, 즉 자바의 &&, ||와 같은 연산을 활용한다.
Optional<T> findAny();
Optional<T> findFirst();
병렬성
때문이다.findFirst
메서드를 사용하고findAny
를 사용한다.isPresent()
: Optional이 값을 포함하면 true, 아니라면 falseifPresent(Consumer<T> block)
: 값이 있으면 주어진 블록 실행T get()
: 값이 존재하면 값 반환, 없으면 NoSuchElementExceptionT orElse(T other)
: 값이 있으면 값 반환, 없으면 기본값 반환T reduce(T identity, BinaryOperator<T> accumulator);
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> { ... }
초기값
과 스트림의 첫 요소
를 가지고 연산한 결과(누적 값, accumulated value)
를 가지고 그 다음 요소와 연산한다.int sum = 0;
for(int x : numbers) {
sum += x;
}
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
int sum = numbers.stream().reduce(0, Integer::sum);
0 + 4 의 결과인 4가 새로운 누적값
누적값으로 람다를 다시 호출하며 다음 요소인 5를 소비
반복하며 마지막 요소 9로 람다를 호출하면 최종적으로 21
Optional<T> reduce(BinaryOperator<T> accumulator);
처음 두 요소를 가지고 연산한 누적값을 사용
Optional 객체 반환한다.
Option<Integer> max = numbers.stream().reduce(Integer::max);
Option<Integer> min = numbers.stream().reduce(Integer::min);
int count = menu.stream().map(d -> 1).reduce(0, (a, b) -> a + b);
map과 reduce를 연결하는 기법을 맵 리듀스 패턴이라 하며, 쉽게 병렬화하는 특징을 이용한 것을 구글이 발표하면서 유명해졌다.
기존 코드에 비해 reduce 메서드의 장점은??
바로 reduce를 이용하면 내부 반복이 추상화되면서 내부 구현에서 병렬
로 reduce를 실행할 수 있게 된다. 기존 코드에서는 sum 변수를 공유해야 하므로 쉽게 병렬화하기 어렵고 동기화의 cost가 매우 크기 때문이다.
스트림을 이용해서 연산을 쉽게 구현할 수 있으며 parrell 메서드를 통해 쉽게 병렬성을 얻을 수 있다.
하지만 스트림 연산은 각각 다양한 연산을 수행하기 때문에 내부적인 상태를 고려해야 한다.
내부 상태가 없는 연산
내부 상태가 있는 연산
스트림 API는 오토박싱 & 언박싱으로 인한 비용을 줄이기 위해 기본형 특화 스트림(primitive stream specialization)을 제공한다.
sum, max 같이 자주 사용하는 숫자 관련 리듀싱 연산 수행 메서드를 제공한다.
IntStream
, DoubleStream
, LongStream
이 존재한다.int sum()
OptionalDouble average()
Optional*Int* max()
Optional*Int* min()
해당 메서드들은 최종연산인 것을 잊지 말아야 한다
sum 메서드를 제외한 나머지 메서드들은 요소가 없을 때 0을 반환할 수 없으므로 이를 구분하기 위해 Optional 래퍼 클래스를 반환
Optional도 기본형에 대하여 지원한다. OptionalInt
, OptionalDouble
, optionalLong
세 가지 기본형 특화 스트림 버전의 Optional이 제공된다.
IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
boxed
메서드를 이용하면 특화 스트림을 일반 스트림으로 변환할 수 있다.Stream<Integer> boxed(); // IntStream to Stream<Integer>
<U> Stream<U> mapToObj(IntFunction<? extends U> mapper); // IntStream to Stream<U>
이제 스트림으로 작업하기 위해 스트림을 생성하는 다양한 방법들을 알아보자
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
Stream.of
을 이용하여 스트림을 만들 수 있다.public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
Stream<String> strStream = Stream.of("hello", "world";)
Stream.ofNullable
메서드를 이용하여 null이 될 수 있는 객체를 지원하는 스트림을 만들 수 있다.public static<T> Stream<T> ofNullable(T t) {
return t == null ? Stream.empty()
: StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}
Arrays.stream
을 이용하여 스트림을 만들 수 있다.public static <T> Stream<T> stream(T[] array)
public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive)
public static *Int*Stream stream(int[] array)
public static *Int*Stream stream(int[] array, int startInclusive, int endExclusive)
range
와 rangeClosed
메서드를 사용할 수 있다.public static IntStream range(int startInclusive, int endExclusive)
public static IntStream rangeClosed(int startInclusive, int endInclusive)
Stream<Path> Files.list(Path dir)
Stream<String> Files.lines(Path path)
Stream.iterate
와 Stream.generate
를 통해 함수를 이용하여 무한 스트림을 만들 수 있다. public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
Stream<Integer> evenStream = Stream.iterate(0, n -> n + 2) // 0, 2, 4, 6, ...
// 0 -> 0 + 2
// 2 -> 2 + 2
// 4 -> 4 + 2
// ....
public static<T> Stream<T> generate(Supplier<? extends T> s)
Stream<Double> randomStream = Stream.generate(Math::random);
public IntStream ints(long streamSize)
public LongStream longs(long streamSize)
public DoubleStream doubles(long streamSize)
InStream intStream = new Random().ints(); // 무한 스트림 o
InStream intStream = new Random().ints(5); // 무한 스트림 x
IntStream.iterate(new int[] {0, 1}, t -> new int[] {t[1], t[0] + t[1]})
.limit(20)
.map(t -> t[0])
.forEach(System.out::println);
IntSupplier fib = new IntSupplier() {
private int prev = 0;
private int cur = 1;
@Override
public int getAsInt() {
int oldPrev = this.prev;
int nextValue = this.prev + this.cur;
this.prev = this.cur;
this.cur = nextValue;
return oldPrev;
}
};
IntStream.generate(fib)
.limit(20)