Stream.reduce

2023. 6. 6. 22:45Java

Stream.reduce스트림의 요소들을 하나의 값으로 축약(reduction) 하는 데 사용되는 종료 연산입니다. 이 연산은 제공된 아이덴티티 값(identity)과 누적 함수(accumulator)를 사용해, 스트림의 모든 요소를 차례대로 처리하고 최종 결과를 반환합니다.

이 메서드는 주어진 두 인자를 받습니다:

  1. 아이덴티티 값 (identity): 누적 함수의 초기값으로, 축약 결과에 영향을 미치지 않는 값입니다. 예를 들어, 합계를 계산할 때 아이덴티티 값은 0, 곱셈을 할 때는 1이 될 수 있습니다.
  2. 누적 함수 (accumulator): 두 값을 입력받아 하나의 결과를 반환하는 함수입니다. 이 함수는 스트림의 각 요소와 이전 연산의 결과를 조합하여 새로운 값을 생성합니다.

reduce는 다음과 같이 작동합니다:

T result = identity;
for (T element : this stream)
    result = accumulator.apply(result, element);
return result;

요구 사항:

  • 아이덴티티 값: 누적 함수에 대해 항등적(즉, 어떤 값과 연산해도 그 값이 변하지 않는 값)이어야 합니다. 예를 들어, Integer 덧셈 연산의 항등값은 0, 곱셈 연산의 항등값은 1입니다.
  • 누적 함수: 이 함수는 결합법칙(associative)을 만족해야 합니다. 즉, 함수의 입력 순서에 상관없이 결과가 같아야 합니다. 예를 들어, a + b(a + b) + c = a + (b + c)의 결합법칙을 따릅니다.

동작 예시

  1. 스트림의 합계 계산:이 예시에서 reduce(0, (a, b) -> a + b)0을 아이덴티티 값으로, (a, b) -> a + b를 누적 함수로 사용하여 스트림의 모든 값을 더합니다. 0은 더하기 연산의 항등값이므로, 첫 번째 요소와 연산을 시작할 수 있습니다.
  2. List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // 누적 함수는 두 값을 더하는 역할을 하고, 아이덴티티 값은 0입니다. int sum = numbers.stream() .reduce(0, (a, b) -> a + b); System.out.println("합계: " + sum); // 출력: 합계: 15
  3. 스트림의 곱셈 계산:여기서 reduce(1, (a, b) -> a * b)1을 아이덴티티 값으로 사용하여 스트림의 모든 값을 곱합니다. 1은 곱셈 연산의 항등값입니다.
  4. List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // 누적 함수는 두 값을 곱하는 역할을 하고, 아이덴티티 값은 1입니다. int product = numbers.stream() .reduce(1, (a, b) -> a * b); System.out.println("곱셈 결과: " + product); // 출력: 곱셈 결과: 120
  5. 스트림에서 최댓값 찾기:여기서는 Math.max(a, b)가 두 값을 비교해 더 큰 값을 반환하는 역할을 합니다. 아이덴티티 값으로 Integer.MIN_VALUE를 사용하여, 모든 값과 비교했을 때 더 작은 값이 없도록 설정합니다.
  6. List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // 누적 함수는 두 값을 비교하여 더 큰 값을 반환합니다. int max = numbers.stream() .reduce(Integer.MIN_VALUE, (a, b) -> Math.max(a, b)); System.out.println("최댓값: " + max); // 출력: 최댓값: 5
  7. 문자열 스트림의 연결:이 경우, 빈 문자열 ""은 문자열 연결의 항등값이며, 스트림의 각 문자열을 차례대로 이어붙입니다.
  8. List<String> words = Arrays.asList("Hello", " ", "World", "!"); // 누적 함수는 두 문자열을 이어붙이는 역할을 하고, 아이덴티티 값은 빈 문자열입니다. String result = words.stream() .reduce("", (a, b) -> a + b); System.out.println("문자열 연결: " + result); // 출력: 문자열 연결: Hello World!

reduce의 병렬 처리 가능성

reduce는 스트림의 데이터를 병렬 처리할 수 있는 방식으로 설계되어 있습니다. 스트림의 각 부분을 별도로 축약하고, 나중에 병합할 수 있기 때문에, 병렬 스트림(parallelStream())에서도 안전하게 사용할 수 있습니다.

병렬 스트림 예시:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

int sum = numbers.parallelStream()
                 .reduce(0, (a, b) -> a + b);

System.out.println("병렬 합계: " + sum);  // 출력: 병렬 합계: 15

이 코드에서는 스트림이 병렬로 처리되어 여러 스레드에서 나누어진 부분 집합을 동시에 처리한 후, 최종적으로 합산합니다.

예시

    // 총 가격 계산 메서드
    public BigDecimal getTotalPrice() {
        return streamable.stream()
                .map(Product::getPrice)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

위 코드는 getTotalPrice라는 메서드로, streamable 컬렉션에 있는 Product 객체들의 가격을 모두 합산하여 총 가격을 계산하는 기능을 합니다. 이 메서드는 Java Stream APIBigDecimal을 사용하여 각 Product의 가격을 축약(reduce) 방식으로 더하고, 최종적으로 합산된 가격을 반환합니다.

코드를 단계별로 설명하면 다음과 같습니다:

1. streamable.stream()

streamableStreamable<Product> 타입의 컬렉션입니다. 이 컬렉션은 Product 객체들의 리스트나 다른 Iterable 타입을 감싸고 있는 객체입니다.

.stream() 메서드를 호출하면, streamable 내부의 Product 객체들을 스트림으로 변환하여 스트림 API를 사용할 수 있게 합니다. 스트림 API를 사용하면 컬렉션 내의 요소들에 대해 반복적인 작업을 수행할 수 있습니다.

2. .map(Product::getPrice)

map 메서드는 스트림의 각 요소(이 경우는 Product 객체)에 대해 주어진 함수를 적용하는 역할을 합니다. 여기서 Product::getPrice메서드 참조 방식으로 각 Product 객체의 getPrice() 메서드를 호출하여 가격(BigDecimal 타입)을 가져옵니다.

즉, streamable.stream()으로 만들어진 Product 객체 스트림에서 각 Product의 가격을 가져와서 가격 스트림으로 변환하는 작업을 수행합니다.

3. .reduce(BigDecimal.ZERO, BigDecimal::add)

reduce 메서드는 스트림의 모든 요소를 축약하여 단일 값으로 만드는 연산을 수행합니다. 이 경우, 스트림의 모든 가격을 더하는 방식으로 작동합니다.

reduce 메서드는 두 개의 인자를 받습니다:

  1. 초기값 (identity): BigDecimal.ZERO는 덧셈 연산에 사용되는 아이덴티티 값으로, 이는 0과 같은 역할을 합니다. BigDecimal.ZEROBigDecimal 타입에서 0을 나타냅니다. 아이덴티티 값은 첫 번째 연산의 시작점이 되며, 축약 중 연산에 영향을 미치지 않는 값입니다.
  2. 누적 함수 (accumulator): BigDecimal::add는 두 개의 BigDecimal 값을 더하는 함수입니다. 이는 스트림의 각 요소를 순차적으로 처리하면서 이전의 누적된 합계와 현재 요소의 가격을 더합니다.

reduce의 동작은 다음과 같습니다:

  • 처음에는 아이덴티티 값인 BigDecimal.ZERO를 기준으로 스트림의 첫 번째 BigDecimal 값(가격)과 더해집니다.
  • 그다음, 두 번째 가격과 누적된 합계를 더하고, 이를 스트림 끝까지 반복합니다.
  • 최종적으로 모든 BigDecimal 값들이 더해져 총 가격을 반환합니다.

전체 코드 흐름

  1. streamable에서 Product 객체들이 스트림으로 변환됩니다.
  2. Product 객체의 가격getPrice() 메서드로 추출하여 가격 스트림을 생성합니다.
  3. reduce 메서드를 사용하여 모든 가격을 더한 총 가격을 계산합니다. 여기서 덧셈은 BigDecimaladd() 메서드를 사용하며, 처음에 BigDecimal.ZERO(0)부터 시작해서 각 가격을 차례대로 더합니다.
  4. 마지막으로, 모든 가격이 더해진 총 가격(BigDecimal 값)을 반환합니다.

예시 코드

class Product {
    private BigDecimal price;

    public Product(BigDecimal price) {
        this.price = price;
    }

    public BigDecimal getPrice() {
        return price;
    }
}

public class Example {
    public static void main(String[] args) {
        List<Product> products = Arrays.asList(
            new Product(new BigDecimal("19.99")),
            new Product(new BigDecimal("5.49")),
            new Product(new BigDecimal("3.79"))
        );

        Streamable<Product> streamable = Streamable.of(products);

        BigDecimal totalPrice = streamable.stream()
                                          .map(Product::getPrice)
                                          .reduce(BigDecimal.ZERO, BigDecimal::add);

        System.out.println("총 가격: " + totalPrice);  // 출력: 총 가격: 29.27
    }
}

주요 개념 요약:

  • Stream: stream() 메서드를 통해 Product 객체들을 스트림으로 변환합니다.
  • map: 각 Product 객체에서 가격을 추출합니다.
  • reduce: 가격을 모두 더하여 하나의 값(총 가격)으로 축약합니다.
  • BigDecimal.ZERO: 덧셈의 시작 값으로 사용됩니다.
  • BigDecimal::add: 가격을 더하는 누적 함수로 사용됩니다.

이 코드는 여러 상품의 가격을 계산할 때 유용하며, BigDecimal을 사용해 정확한 금액 계산을 처리합니다.

요약

  • reduce는 스트림의 요소들을 하나의 값으로 축약하는 메서드입니다.
  • 아이덴티티 값은 연산에서 항등적이어야 하며, 누적 함수는 결합법칙을 만족해야 합니다.
  • 병렬 처리에서도 안전하게 사용할 수 있으며, 여러 가지 축약 연산(합계, 곱셈, 최댓값/최솟값, 문자열 연결 등)에 활용할 수 있습니다.

이를 통해 스트림의 데이터를 손쉽게 축약하여 처리할 수 있습니다.

'Java' 카테고리의 다른 글

Callable & ExecutorService  (0) 2023.06.23
Cloneable 인터페이스(06/12)  (0) 2023.06.06
Class & Instance Copy(06/12)  (0) 2023.06.06
native 키워드  (0) 2023.06.06
record  (0) 2023.06.04