자바 스트림 정리3-스트림 결과연산

Published: by Creative Commons Licence

오늘은 스트림의 중간연산을 거쳐 가공한 스트림을 결과로 만들어내는 단말 연산(Terminal Operations)에 대해서 정리해 보려고 한다.

forEach(forEachOrdered)

forEach 메소드는 병렬 스트림을 사용하면 순서가 보장되지 않는다. 스트림을 순서를 보존하고자 한다면 forEachOrdered 메소드를 사용한다.

@Test
void test1(){
    List<Integer> list = List.of(1, 2, 3, 4, 5);

    // 매 실행마다 출력 결과가 동일하지 않다.
    list.parallelStream().forEach(System.out::println);

    System.out.println("====================================================");

    // 매 실행마다 동일한 출력 결과
    list.parallelStream().forEachOrdered(System.out::println);
}

Reducation

reduce 연산을 이용해 모든 스트림 요소를 처리해 결과를 구할 수 있다. reduce 메소드는 총 세가지의 파라미터를 받을 수 있다.

  1. accumulator : 각 요소를 처리하는 계산 로직. 각 요소가 올 때마다 중간 결과를 생성하는 로직.
  2. identity : 계산을 위한 초기값으로 스트림이 비어서 계산할 내용이 없더라도 이 값은 리턴.
  3. combiner : 병렬(parallel) 스트림에서 나눠 계산한 결과를 하나로 합치는 동작하는 로직.
// 1개 (accumulator)
Optional<T> reduce(BinaryOperator<T> accumulator);

// 2개 (identity)
T reduce(T identity, BinaryOperator<T> accumulator);

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

값을 누적하는 연산의 경우 병렬 연산의 결과를 결합해야 하는데, 여기서 세 번째 인자가 그 역할을 한다. 병렬 처리를 하는 경우에 각자 다른 스레드의 결과를 합쳐준다. 일반 스트림에서는 in combiner 내부로직이 수행안되고 병렬스트림에서는 in combiner가 수행된다.

@Test
void test2(){
    // BinaryOperator를 사용하는데 이는 두 개의 같은 타입 요소를 인자로 받아 동일한 타입의 결과를 반환하는 함수형 인터페이스를 사용합니다.
    List<Integer> list = List.of(1, 2, 3);
    Optional<Integer> result = list.stream().reduce((a, b) -> a + b);
    System.out.println("BinaryOperator lambda : " + result.get());
    System.out.println("=================================================");
    System.out.println("BinaryOperator : " + list.stream().reduce(Integer::sum).get());
    System.out.println("=================================================");
    System.out.println("BinaryOperator 초기값 : " + list.stream().reduce(4, Integer::sum));
}

@Test
void test3(){
    List<Integer> list = List.of(1, 2, 5);
    Integer result = list.stream()
    .reduce(1, Integer::sum, (a, b) -> {
        System.out.println("in combiner - a:" + a + " b:" + b);
        return a + b;
    });
    System.out.println(result);

    System.out.println("=================================================");

    Integer result2 = list.parallelStream()
    .reduce(1, Integer::sum, (a, b) -> {
        System.out.println("in combiner - a:" + a + " b:" + b);
        return a + b;
    });
    System.out.println(result2);
}

min, max, count, sum, average

@Test
void test4(){
    // min
    OptionalDouble min = DoubleStream.of(4.12, 5.13, -1.32, 123.9, -15.7).min();
    min.ifPresent(System.out::println);

    System.out.println("=================================================");

    // max
    int max = IntStream.of(100, 400, 500, 300).max().getAsInt();
    System.out.println(max);
    System.out.println("=================================================");

    // count
    long count = IntStream.of(2, 4, 1, 3, 5, 7, 6).count();
    System.out.println(count);

    System.out.println("=================================================");

    // sum
    double sum = DoubleStream.of(3.0, 2.0, 1.0, 4.0).sum();
    System.out.println(sum);

    System.out.println("=================================================");

    // average
    OptionalDouble average = IntStream.of(3, 2, 1, 4, 5).average();
    average.ifPresent(System.out::println);
}

결과 모으기(Collector)

Collectors.toList: 작업 결과를 리스트로 반환

map으로 Food Object에서 name만을 추출 List 객체로 반환한다.

List<Food> list = new ArrayList<>();
list.add(new Food("burger", 520));
list.add(new Food("chips", 230));
list.add(new Food("coke", 143));
list.add(new Food("soda", 143));

List<String> nameList = list.stream()
        .map(Food::getName)
        .collect(Collectors.toList());
nameList.forEach(System.out::println);
System.out.println("=================================================");

숫자값의 합,평균구하기

Integer summingName = list.stream()
        .collect(Collectors.summingInt(s -> s.getName().length()));
System.out.println(summingName);
System.out.println("=================================================");

// mapToInt 메서드로 칼로리(cal) 합 구하기
int sum = list.stream().mapToInt(Food::getCal).sum();
System.out.println(sum);
System.out.println("=================================================");

// Collectors.averagingInt 평균 구하기
Double averageInt = list.stream()
        .collect(Collectors.averagingInt(Food::getCal));
System.out.println(averageInt);
System.out.println("=================================================");

// Collectors.averagingDouble 평균 구하기
Double averageDouble = list.stream()
        .collect(Collectors.averagingDouble(Food::getCal));
System.out.println(averageDouble);
System.out.println("=================================================");

Collectors.summarizingInt 통계 메소드

Collectors.summarizingInt 를 이용해 평균, 개수, 최대값, 최소값, 합계를 구한다.

IntSummaryStatistics summaryStatistics = list.stream()
        .collect(Collectors.summarizingInt(Food::getCal));

System.out.println("평균 : " + summaryStatistics.getAverage());
System.out.println("개수 : " + summaryStatistics.getCount());
System.out.println("최댓값 : " + summaryStatistics.getMax());
System.out.println("최솟값 : " + summaryStatistics.getMin());
System.out.println("합계 : " + summaryStatistics.getSum());

Collectors.joining 스트림 연산 결과를 하나의 문자열로 만들기

  • 인자가 없는 경우
  • 구분자를 인자를 넣는 경우
  • 구분자,prefix,suffix를 인자를 받는 경우
    void test6(){
      List<Food> list = new ArrayList<>();
      list.add(new Food("aaa", 300));
      list.add(new Food("bbb", 400));
      list.add(new Food("ccc", 100));
      list.add(new Food("ddd", 100));
        
      // without arguments
      String defaultJoining = list.stream()
              .map(Food::getName).collect(Collectors.joining());
        
      System.out.println(defaultJoining);
        
      // delimiter
      String delimiterJoining = list.stream()
              .map(Food::getName).collect(Collectors.joining(","));
        
      System.out.println(delimiterJoining);
        
      // delimiter, prefix, suffix
      String combineJoining = list.stream()
              .map(Food::getName).collect(Collectors.joining(",", "[", "]"));
        
      System.out.println(combineJoining);
    }
    

Collectors.groupingBy

스트림 내의 요소들을 주어진 조건으로 그룹핑(Grouping)

List<Food> list = new ArrayList<>();
list.add(new Food("aaa", 300));
list.add(new Food("bbb", 400));
list.add(new Food("ccc", 100));
list.add(new Food("ddd", 100));

// 칼로리(cal)로 그룹 만들기
Map<Integer, List<Food>> calMap = list.stream()
        .collect(Collectors.groupingBy(Food::getCal));

System.out.println(calMap);

Collectors.partitioningBy

partitioningBy는 인자로 Predicate 함수형 인터페이스를 받는다. Predicate는 인자를 받아서 참 또는 거짓 boolean 값으로 그룹핑한다.

List<Food> list = new ArrayList<>();
list.add(new Food("aaa", 300));
list.add(new Food("bbb", 400));
list.add(new Food("ccc", 100));
list.add(new Food("ddd", 100));

Map<Boolean, List<Food>> partitionMap = list.stream()
            .collect(Collectors.partitioningBy(food -> food.getCal() > 100));

System.out.println(partitionMap);

Collectors.toMap

키 값이 2개 이상 존재하면 toMap 처리중 IllegalStateException이 발생한다. 그래서 중복키가 있는 경우 BinaryOperator 인자를 추가해야 한다.

@Test
void test8() {
    List<Food> list = new ArrayList<>();
    list.add(new Food("aaa", 300));
    list.add(new Food("bbb", 400));
    list.add(new Food("ccc", 100));
    list.add(new Food("ddd", 100));

    // 동일한 키가 있는 경우 새 값으로 대체한다.
    Map<Integer, String> map = list.stream()
            .collect(Collectors.toMap(
                    o -> o.getCal(),
                    o -> o.getName(),
                    (oldValue, newValue) -> newValue));

    System.out.println(map);
}

Match 조건체크

Predicate 조건식을 인자로 받아서 해당 조건을 만족하는 요소가 있는지 체크한다.

@Test
void test10() {
    List<Food> list = new ArrayList<>();
    list.add(new Food("aaa", 300));
    list.add(new Food("bbb", 400));
    list.add(new Food("ccc", 100));
    list.add(new Food("ddd", 100));

    boolean anyMatch = list.stream()
            .anyMatch(food -> food.getCal() > 300);
    assertTrue(anyMatch);

    boolean allMatch = list.stream()
            .allMatch(food -> food.getCal() > 100);
    assertFalse(allMatch);

    boolean noneMatch = list.stream()
            .noneMatch(food -> food.getCal() < 1000);
    assertFalse(noneMatch);
}

참고

자바 스트림 정리: 3. 스트림 결과 구하기
Java 스트림 Stream (1) 총정리

Github

https://github.com/sisipapa/StreamExample.git