[Java] 스트림과 병렬 처리 ⑤

kiteB·2022년 3월 5일
0

Java2

목록 보기
20/36
post-thumbnail

[ 매칭 (allMatch(), anyMatch(), noneMatch()) ]

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

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

✅ 예제

int[] 배열로부터 스트림을 생성한 뒤

  • 모든 요소가 2의 배수인지,
  • 하나라도 3의 배수가 존재하는지,
  • 모든 요소가 3의 배수가 아닌지를 조사한다.
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의 배수인가? true
3의 배수가 하나라도 있는가? true
3의 배수가 없는가? false

[ 기본 집계 (sum(), count(), average(), max(), min()) ]

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

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

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

✅ 기본 집계 메소드

스트림은 다음과 같이 기본 집계 메소드를 제공하고 있다.

OptionalXXX

  • java.util 패키지의 Optional, OptionalDouble, OptionalInt, OptionalLong 클래스 타입을 말한다.
  • 값을 저장하는 값 기반 클래스(value-based class)이다.
  • 이 객체에서 값을 얻기 위해서는 get(), getAsDouble(), getAsInt(), getAsLong()을 호출하면 된다.

✅ 예제 | 집계

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("2의 배수 중 최댓값: " + max);


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

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


    }
}
  • 실행 결과
2의 배수 개수: 2
2의 배수의 합: 6
2의 배수의 평균: 3.0
2의 배수 중 최댓값: 4
2의 배수 중 최솟값: 2
첫 번째 3의 배수: 3

2. Optional 클래스

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

✅ Optional 클래스 메소드

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

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

컬렉션의 요소는 동적으로 추가되는 경우가 많다.

만약 아래 코드와 같이 컬렉션의 요소가 추가되지 않아 저장된 요소가 없을 경우,

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

(요소가 없으므로) 당연히 평균값도 없기 때문에 NoSuchElementException 예외가 발생한다.


✅ 요소가 없을 경우 예외를 피하는 방법

NoSuchElementException 예외가 발생하는 상황을 방지하기 위해 세 가지 방법을 사용할 수 있다.

1. Optional 객체를 얻어 isPresent() 메소드로 평균값 여부를 확인하기

  • isPresent() 메소드가 true를 리턴할 때만 getAsDouble() 메소드로 평균값을 얻으면 된다.
OptionalDouble optional = list.stream()
        .mapToInt(Integer :: intValue)
        .average();

if (optional.isPresent()) {
    System.out.println("평균: " + optional.getAsDouble());
} else {
    System.out.println("평균: 0.0");
}

2. orElse() 메소드로 디폴트 값 정해놓기

  • 평균값을 구할 수 없는 경우에는 orElse()의 매개값이 디폴트 값이 된다.
double avg = list.stream()
        .mapToInt(Integer::intValue)
        .average()
        .orElse(0.0);
System.out.println("평균: " + avg);

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

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

전체 코드

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();
         */

        //방법 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));
    }
}
  • 실행 결과
방법1_평균: 0.0
방법2_평균: 0.0

[ 커스텀 집계 (reduce()) ]

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

  • 각 인터페이스에는 매개 타입으로 XXXOperator, 리턴 타입으로 OptionalXXX, int, long, double을 가지는 reduce() 메소드가 오버로딩되어 있다.
  • 스트림에 요소가 전혀 없을 경우 디폴트 값인 identity 매개값이 리턴된다.

✅ XXXOperator

XXXOperator 매개값집계 처리를 위한 람다식을 대입한다.

예를 들어 학생들의 총점은 학생 스트림에서 점수 스트림으로 매핑해서 다음과 같이 얻을 수 있다.

int sum = studentList.stream()
        .map(Student::getScore)
        .reduce((a, b) -> a + b)
        .get();

위의 코드는 스트림에 요소가 없을 경우 NoSuchElementException이 발생하지만,
아래의 코드는 디폴트 값인 0을 리턴한다.

int sum = studentList.stream()
        .map(Student :: getScore)
        .reduce(0, (a, b) -> a + b);

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


✅ 예제 | reduce() 메소드 이용

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(BinaryOperator<Integer> ac) 이용
        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);

    }
}
  • 실행 결과
sum1: 275
sum2: 275
sum3: 275

[ 참고자료 ]

이것이 자바다 책

profile
🚧 https://coji.tistory.com/ 🏠

0개의 댓글