Java 8 Stream API 살펴보기 -2- Stream 가공하기 / 결과 만들기

Updated:

1. Intro

본 포스트에서는 Stream 에서 데이터를 가공하고, 결과를 반환하는 메소드에 대해 알아보겠습니다.

함수가 다양하고 많기 때문에 간단하게 설명하면서 진행하겠습니다.

  • 가공하기
    • Filtering
    • Mapping
    • Sorting
    • Iterating
  • 결과 만들기
    • Calculating
    • Reduction
    • Collecting
    • Matching
    • Iterating

결과 만들기에서 findAny(), findFirst() 함수는 다음 포스트에서 다루겠습니다.

2. 가공하기

2.1. Filtering

2.1.1. filter

필터(filter) 는 Stream 내 요소들을 하나씩 비교하여 걸러내는 메소드입니다. 인자로 받는 Predicateboolean 을 리턴하는 함수형 인터페이스이며, 조건식을 표현하는 데 사용됩니다.

Stream<T> filter(Predicate<? super T> predicate);

Stream 에서 “A” 와 동일한 요소를 걸러내는 예제입니다.

List<String> list = Arrays.asList("A", "B", "C", "D");
Stream<String> stream = list.stream()
                            .filter(s -> s.equals("A")); // [A]

2.1.2. limit

최대 maxSize 까지 Stream 을 리턴합니다. 보통 generate() 와 같이 사용합니다.

Stream<T> limit(long maxSize);
Stream<String> stream = Stream.generate(() -> "element").limit(2);
// [element, element]

2.1.3. limit

Stream 의 상위 n 개 요소를 생략한 Stream 을 리턴합니다.

Stream<T> skip(long n);
List<Integer> list = Arrays.asList(5, 7, 3, 1, 2, 6, 1, 9, 0);
Stream<Integer> stream = list.stream()
                             .sorted(Comparator.reverseOrder())
                             .skip(4); // [3, 2, 1, 1, 0]

2.1.4. distinct

중복제거(distinct) 는 Stream 에서 중복된 요소를 제거한 Stream 을 리턴합니다.

Stream<T> distinct();

간단한 예제를 보겠습니다.

List<String> list = Arrays.asList("A", "A", "B", "B", "C", "C");
Stream<String> stream = list.stream()
                            .distinct(); // [A, B, C]

2.2. Mapping

2.2.1. map

맵(map) 은 Stream 내 요소들을 하나씩 특정 값으로 변환합니다. 이때 람다를 인자로 받습니다.

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

문자열을 소문자로 바꿔주는 예제입니다.

List<String> list = Arrays.asList("A", "B", "C", "D");
Stream<String> stream = list.stream()
                            .map(String::toLowerCase); // [a, b, c, d]

2.2.2. mapToInt, mapToLong, mapToDouble

mapToInt(), mapToLong(), mapToDouble() 함수들은 Stream 을 해당하는 타입으로 바꿔줍니다.

IntStream mapToInt(ToIntFunction<? super T> mapper);

LongStream mapToLong(ToLongFunction<? super T> mapper);

DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);

mapToInt() 를 예시로 간단한 예제를 알아보겠습니다.

List<String> list = Arrays.asList("1", "2", "3", "4");
IntStream stream = list.stream()
                        .mapToInt(s -> Integer.parseInt(s)); // [1, 2, 3, 4]

2.2.3. flatMap, flatMapToInt, flatMapToLong, flatMapToDouble

flatMap() 은 조금 더 복잡합니다. 인자로 mapper 를 받고 리턴 타입이 Stream 입니다. 새로운 Stream 을 생성해서 리턴하는 람다를 넘겨야 하는데, flatMap 은 중첩 구조를 한 단계 제거하고 단일 컬렉션으로 만들어줍니다.

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

간단한 예제로 살펴보겠습니다.

List<List<String>> list =  Arrays.asList(Arrays.asList("a"), Arrays.asList("b")); // [[a], [b]]
List<String> flatMap = list.stream()
                            .flatMap(Collection::stream)
                            .collect(Collectors.toList()); // [a, b]

flatMapToInt(), flatMapToLong(), flatMapToDouble() 는 위에서 설명한 mapToInt() 와 동일합니다.

2.3. Sorting

2.3.1. sorted

정렬도 다른 정렬과 동일하게 Comparator 를 사용합니다.

Stream<T> sorted(Comparator<? super T> comparator);

인자 없이 출력하면 디폴트 값은 오름차순입니다.

List<Integer> list = Arrays.asList(5, 7, 3, 1, 2, 6, 1, 9, 0);
Stream<Integer> stream = list.stream()
                             .sorted(); // [0, 1, 1, 2, 3, 4, 5, 6, 7, 9]
List<Integer> list = Arrays.asList(5, 7, 3, 1, 2, 6, 1, 9, 0);
Stream<Integer> stream = list.stream()
                             .sorted(Comparator.reverseOrder());
                             // [9, 7, 6, 5, 4, 3, 2, 1, 1, 0]

2.4. Iterating

2.4.1 peak

특정 결과를 반환하지 않는 Consumer 를 인자로 받으며, Stream 내 특정 작업을 수행할 뿐 결과에 영향을 미치지 않습니다. 아래 예제처럼 작업을 처리하는 중간에 결과를 확인할 때 사용할 수 있습니다.

Stream<T> peek(Consumer<? super T> action);
List<Integer> list = Arrays.asList(5, 7, 3, 1, 2, 6, 1, 9, 0);
Stream<Integer> stream = list.stream()
                             .peek(System.out::println)
                             .sorted(Comparator.reverseOrder());

3. 결과 만들기

3.1. Reduction

Stream 은 reduce() 라는 메소드를 이용해 결과를 만듭니다. 이 메소드는 3 가지의 파라미터를 가지고 있습니다.

  • accumulator : 각 요소를 처리하는 계산 로직
  • identity : 계산을 위한 초깃값으로 Stream 이 비어서 계산할 내용이 없더라도 리턴한다
  • combiner : 병렬 Stream 처리할 때 나눠서 계산한 결과를 하나로 합치는 로직.
Optional<T> reduce(BinaryOperator<T> accumulator);

T reduce(T identity, BinaryOperator<T> accumulator);

<U> U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator<U> combiner);

각각 인자별로 예제를 살펴보겠습니다.

// 45
OptionalInt reduce = 
    IntStream.range(1, 10)
             .reduce((a, b) -> { return a + b; });
// 55
Integer reduce = 
    IntStream.range(1, 10)
             .reduce(10, Integer::sum);
// 36
Integer reduce = 
    Stream.of(1, 2, 3)
          .parallel()
          .reduce(10, Integer::sum, (a, b) -> { return a + b; });

3.2. Collecting

필요한 요소룰 수집하여 새로운 Collection 으로 반환하는 메소드입니다. 이번 포스트에서는 간단히 다루고 추후 toList(), joining() 과 같은 세부 메소드를 다루겠습니다.

<R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);

<R, A> R collect(Collector<? super T, A, R> collector);

아래 예제는 문자열 Stream 에서 “b” 가 포함된 문자를 filter 를 통하여 거르고 collect() 메소드를 이용하여 다시 List 로 만들어주는 예제입니다.

List<String> list =
    Stream.of("a", "b", "c").filter(element -> element.contains("b"))
                            .collect(Collectors.toList());

Collector 에는 다양한 메소드가 존재하기 때문에 Java 8 Stream API 살펴보기 -4- Colector 살펴보기 위 포스에서 상세하게 다루겠습니다.

3.3. Calculating

기본형 Stream 의 통계가 있으며 T 타입 Stream 의 통계가 있습니다.

Optional<T> min(Comparator<? super T> comparator);

Optional<T> max(Comparator<? super T> comparator);

long count();

기본형 Stream 통계

  • count()
  • sum()
  • average()
  • min()
  • max()

3.4. Matching

3.4.1. anyMatch, allMatch, noneMatch

Stream 에서 찾고자 하는 객체가 존재하는지 탐색을 하고 boolean 타입을 리턴합니다. 메소드는 anyMatch(), allMatch(), noneMatch() 가 있습니다.

  • anyMatch() 는 하나라도 조건에 맞는 요소가 있으면 true 를 리턴
  • allMatch() 는 모든 요소가 조건에 맞아야 true 를 리턴
  • noneMatch() 는 조건에 맞는 객체가 없어야 true 를 리턴
boolean anyMatch(Predicate<? super T> predicate);

boolean allMatch(Predicate<? super T> predicate);

boolean noneMatch(Predicate<? super T> predicate);

예제를 통해 한번에 알아보겠습니다.

List<String> list = Arrays.asList("A", "B", "C", "D");

boolean anyMatch = list.stream()
                        .anyMatch(s -> s.contains("A")); // true
boolean allMatch = list.stream()
                        .allMatch(s -> s.contains("A")); // false
boolean noneMatch = list.stream()
                        .noneMatch(s -> s.contains("A")); // false

3.5. Iterating

3.5.1. forEach

forEach() 는 요소를 돌면서 실행하는 최종 작업입니다. peek() 와의 차이는 중간 작업이냐, 최종 작업이냐의 차이가 있습니다.

void forEach(Consumer<? super T> action);
List<Integer> list = Arrays.asList(1, 2, 3);
list.stream().forEach(System.out::println); // 1 2 3

3.5.2. forEachOrdered

병렬처리에서 순서를 보장할 때 사용할 수 있습니다.

void forEachOrdered(Consumer<? super T> action);
// 651728934
IntStream.range(1, 10).parallel().forEach(System.out::print);
// 123456789
IntStream.range(1, 10).parallel().forEachOrdered(System.out::print);

참고자료

Leave a comment