개발자는 기록이 답이다

스트림과 병렬처리(3) - 매칭, 기본집계, 커스텀 집계 본문

언어/Java

스트림과 병렬처리(3) - 매칭, 기본집계, 커스텀 집계

slow-walker 2023. 12. 4. 03:25

1. 매칭(allMatch(), anyMatch(), noneMatch())

 

스트림 클래스는 최종 처리 단계에서 요소들이 특정 조건에 만족하는지 조사할 수 있도록 세 가지 매칭 메소드를 제공한다.

  • allMatch()메소드는 모든 요소들이 매개값으로 주어진 Predicate의 조건을 만족하는지 조사하고,
  • anyMatch()메소드는 최소한 한 개의 요소가 매개값으로 주어진 Predicate의 조건을 만족하는지 조사한다.
  • noneMatch()메소드는 모든 요소들이 매개값으로 주어진 PRedicate의 조건을 만족하지 않는지 조사한다.

아래 예제는 int[]배열로부터 스트림을 생성하고, 모든 요소가 2의 배수인지, 하나라도 3의 배수가 존재하는지, 모든 요소가 3의 배수가 아닌지 조사한다.

package org.example.chapter16.matching;

import java.util.Arrays;

public class MatchExample {

    public static void main(String[] args) {
        int[] intArr = { 2, 4, 6};

        boolean result = Arrays.stream(intArr)
                .allMatch(a -> a % 2 == 0);
        System.out.println("모두 2의 배수인가? " + result);

        result = Arrays.stream(intArr)
                .anyMatch(a -> a % 3 == 0);
        System.out.println("하나라도 3의 배수인가? " + result);

        result = Arrays.stream(intArr)
                .noneMatch(a -> a % 3 == 0);
        System.out.println("3의 배수가 없는가? " + result);
    }
}

 

2. 기본집계(sum(), count(), average(), max(), min())

 

집계(Aggregate)는 최종 처리 기능으로 요소들을 처리해서 카운팅, 합계, 평균값, 최대값, 최소값 등과 같이 하나의 값으로 산출한다.

집계는 대량의 데이터를 가공해서 축소하는 리덕션(Reduction)이라고 볼 수 있다.

 

스트림이 제공하는 기본 집계

 

 

기본 집계 메소드에서 리턴하는 OptionalXXX는 자바 8에서 추가한 java.util 패키지의 Optional, OptionalDouble, OptionalInt, OptionalLong클래스 타입을 말한다. 이들은 값을 저장하는 값 기반 클래스(value-base class)들이다.

이 객체에서 값을 얻기 위해서는 get(), getAsDouble(), getAsLong()을 호출하면 된다.

package org.example.chapter16.aggregate;

import java.util.Arrays;

public class AggregateExample {
    public static void main(String[] args) {
        long count = Arrays.stream(new int[] {1, 2, 3, 4, 5})
                .filter(n -> n%2 ==0)
                .count();
        System.out.println("2의 배수 개수: " + count);

        long sum = Arrays.stream(new int[] {1, 2, 3, 4, 5})
                .filter(n -> n%2 ==0)
                .sum();
        System.out.println("2의 배수의 합: " + sum);

        double avg = Arrays.stream(new int[] {1, 2, 3, 4, 5})
                .filter(n -> n%2 ==0)
                .average()
                .getAsDouble();
        System.out.println("2의 배수의 평균: " + avg);

        int max = Arrays.stream(new int[] {1, 2, 3, 4, 5})
                .filter(n -> n%2 ==0)
                .max()
                .getAsInt();
        System.out.println("최대값: " + max);

        int min = Arrays.stream(new int[] {1, 2, 3, 4, 5})
                .filter(n -> n%2 ==0)
                .min()
                .getAsInt();
        System.out.println("최소값: " + min);

        int first = Arrays.stream(new int[] {1, 2, 3, 4, 5})
                .filter(n -> n%3 ==0)
                .findFirst()
                .getAsInt();
        System.out.println("첫번째 3의 배수: " + first);

    }
}

 

 

Optional 클래스

 

Optional, OptionalDouble, OptionalInt, OptionalLong 클래스에 대해서 좀 더 알아보자.

이 클래스들은 저장하는 값의 타입만 다를 뿐 제공하는 기능은 거의 동일하다.

 

Optional 클래스는 단순히 집계 값만 저장하는 것이 아니라,

집계 값이 존재하지 않을 경우 디폴트 값을 설정할 수도 있고, 집계 값을 처리하는 Consumer도 등록할 수 있다.

 

컬렉션의 요소는 동적으로 추가되는 경우가 많다. 만약 컬렉션의 요소가 추가되지 않아 저장된 요소가 없을 경우 아래 코드는 어떻게 될까?

List<Integer> list = new ArrayList<>();
double avg = list.stream()
	.mapToInt(Integer :: intValue)
	.average()
	.getAsDouble();
System.out.println("평균: " + avg);

 

요소가 없기 때문에 평균값도 있을 수 없다. 그래서 NoSuchElementException예외가 발생한다.

 

요소가 없을 경우 예외를 피하는 3가지 방법이 있다.

 

Optional객체를 얻어 isPresent()메소드로 평균값 여부를 확인하는 것이다

isPresent()메소드가 true를 리턴할때만 getAsDouble()메소드로 평균값을 얻으면 된다.

List<Integer> list = new ArrayList<>();

OptionalDouble optional = list.stream()
	.mapToInt(Integer :: intValue)
	.average();
if(optional.isPresent()) {
	System.out.println("평균: " + optional.getAsDouble());
} else {
	System.out.println("평균: 0.0");
}

 

orElse()메소드로 디폴트 값을 정해 놓는다. 평균값을 구할 수 없는 경우에는 orElse()의 매개값이 디폴트 값이 된다.

List<Integer> list = new ArrayList<>();

double avg = list.stream()
	.mapToInt(Integer :: intValue)
	.average()
	.orElse(0.0);
System.out.println("평균: " + avg);

 

ifPresent()메소드로 평균값이 있을 경우에만 값을 이용하는 람다식을 실행한다.

list.stream()
	.mapToInt(Integer :: intValue)
	.average()
	.ifPresent(a -> System.out.println("평균: " + a));
package org.example.chapter16.optional;

import java.util.ArrayList;
import java.util.List;
import java.util.OptionalDouble;

public class OptionalExample {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();

        /*// 예외 발생(java.util.NoSuchElementException)
        double avg = list.stream()
                .mapToInt(Integer :: intValue)
                .average()
                .getAsDouble();
        System.out.println("평균: " + avg);
         */

        //방법1
        OptionalDouble optional = list.stream()
                .mapToInt(Integer :: intValue)
                .average();
        if(optional.isPresent()) {
            System.out.println("방법1 평균: " + optional.getAsDouble());
        } else {
            System.out.println("방법1 평균: 0.0");
        }

        //방법2
        double avg = list.stream()
                .mapToInt(Integer :: intValue)
                .average()
                .orElse(0.0);
        System.out.println("방법2 평균: " + avg);

        //방법3
        list.stream()
                .mapToInt(Integer :: intValue)
                .average()
                .ifPresent(a -> System.out.println("방법3 평균: " + a));
    }
}

 

3. 커스텀 집계(reduce())

 

스트림은 기본 집계 메소드인 sum(), count(), average(), max(), min()을 제공하지만, 프로그램화 해서 다양한 집계 결과물을 만들 수 있도록 reduce()메소드도 제공한다.

 

각 인터페이스에는 매개 타입으로 XXXOperator, 리턴 타입으로 OperatorXXX, int, long, double 을 가지는 reduce()메소드가 오버로딩되어있다. 스트림에는 요소가 전혀 없을 경우 디폴트 값인 identity 매개값이 리턴된다. XXXOperator 매개값은 집계 처리를 위한 람다식을 대입하는데, 예를 들어 학생들의 성적 총점은 학생 스트림에서 점수 스트림으로 매핑해서 아래와 같이 얻을 수 있다.

좌측 코드는 스트림에 요소가 없을 경우 NoSuchElementException이 발생하지만, 우측 코드는 디폴트 값(identity)인 0을 리턴한다.

스트림에 요소가 있을 경우에는 두 코드 모두 동일한 결과를 산출한다.

package org.example.chapter16.reduce;

import org.example.chapter16.Student;

import java.util.Arrays;
import java.util.List;

public class ReductionExample {
    public static void main(String[] args) {
        List<Student> studentList = Arrays.asList(
                new Student("홍길동",92),
                new Student("신용권",95),
                new Student("감자바",88)
        );
        //sum()이용
        int sum1 = studentList.stream()
                .mapToInt(Student :: getScore)
                .sum();
        
        //reduce(IntBinaryOperator op이용)
        int sum2 = studentList.stream()
                .map(Student :: getScore)
                .reduce((a, b) -> a + b)
                .get();
        
        //reduce(int identity, IntBinaryOperator op이용)
        int sum3 = studentList.stream()
                .map(Student :: getScore)
                .reduce(0, (a, b) -> a + b);

        System.out.println("sum1: " + sum1);
        System.out.println("sum2: " + sum2);
        System.out.println("sum3: " + sum3);
    }
}