스트림 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)