스트림 수집기의 실제 예

Java Stream의Collectors 메서드는 대부분의 사용 사례에 적합합니다. Collection 또는 스칼라를 반환할 수 있습니다. 전자의 경우 toXXX() 방법 중 하나를 사용하고 후자의 경우 reducing() 방법 중 하나를 사용합니다.

장바구니를 구현하는 전자 상거래 플랫폼을 상상해 봅시다. 카트는 다음과 같이 모델링됩니다.



이 다이어그램은 다음(축약된) 코드로 변환될 수 있습니다.

public class Product {

    private final Long id;                           // 1
    private final String label;                      // 1
    private final BigDecimal price;                  // 1

    public Product(Long id, String label, BigDecimal price) {
        this.id = id;
        this.label = label;
        this.price = price;
    }

    @Override
    public boolean equals(Object object ) { ... }    // 2

    @Override
    public int hashCode() { ... }                    // 2
}

  • 게터
  • id에만 의존

  • public class Cart {
    
        private final Map<Product, Integer> products = new HashMap<>(); // 1
    
        public void add(Product product) {
            add(product, 1);
        }
    
        public void add(Product product, int quantity) {
            products.merge(product, quantity, Integer::sum);
        }
    
        public void remove(Product product) {
            products.remove(product);
        }
    
        public void setQuantity(Product product, int quantity) {
            products.put(product, quantity);
        }
    
        public Map<Product, Integer> getProducts() {
            return Collections.unmodifiableMap(products);               // 2
        }
    }
    

  • 제품을 지도로 구성합니다. 키는 Product입니다. 값은 수량입니다.
  • 캡슐화를 유지하려면 컬렉션의 읽기 전용 복사본을 반환해야 합니다.

  • 데이터를 메모리에 저장하는 방법을 정의했으면 카트를 화면에 표시하는 방법을 디자인해야 합니다. 결제 화면에는 두 가지 다른 정보가 표시되어야 합니다.
  • 각 행의 가격, 즉 제품당 가격과 수량을 곱한 행 목록입니다.
  • 장바구니의 전체 가격입니다.

  • 다음은 해당 코드입니다.

    public record CartRow(Product product, int quantity) {                // 1
    
        public CartRow(Map.Entry<Product, Integer> entry) {
            this(entry.getKey(), entry.getValue());
        }
    
        public BigDecimal getRowPrice() {
            return product.getPrice().multiply(new BigDecimal(quantity));
        }
    }
    

  • CartRow는 값 개체입니다.
    Java 16record으로 모델링할 수 있습니다.

  • var rows = cart.getProducts()
        .entrySet()
        .stream()
        .map(CartRow::new)
        .collect(Collectors.toList());                                    // 1
    
    var price = cart.getProducts()
        .entrySet()
        .stream()
        .map(CartRow::new)
        .map(CartRow::getRowPrice)                                        // 2
        .reduce(BigDecimal.ZERO, BigDecimal::add);                        // 3
    

  • 행 목록을 수집합니다.
  • 각 행의 가격을 계산합니다.
  • 총 가격을 계산합니다.

  • Java 스트림의 주요 제한 사항 중 하나는 스트림을 한 번만 사용할 수 있다는 것입니다. 그 이유는 스트리밍 객체가 반드시 불변인 것은 아니기 때문입니다(불변할 수는 있지만). 따라서 동일한 스트림을 두 번 실행하는 것은 멱등성이 아닐 수 있습니다.

    따라서 행과 가격을 모두 가져오려면 카트에서 두 개의 스트림을 생성해야 합니다. 한 스트림에서 행을 가져오고 다른 스트림에서 가격을 가져옵니다.
    이것은 방법이 아닙니다.

    단일 스트림에서 행과 가격을 모두 수집하려고 합니다. 단일 개체로 한 번에 둘 다 반환하는 사용자 정의Collector가 필요합니다.

    public class PriceAndRows {
    
        private BigDecimal price;                             // 1
        private final List<CartRow> rows = new ArrayList<>();  // 2
    
        PriceAndRows(BigDecimal price, List<CartRow> rows) {
            this.price = price;
            this.rows.addAll(rows);
        }
    
        PriceAndRows() {
            this(BigDecimal.ZERO, new ArrayList<>());
        }
    }
    

  • 총 장바구니 가격입니다.
  • 제품 레이블, 제품 가격 및 행 가격을 표시할 수 있는 카트 행 목록입니다.

  • 다음은 Collector 인터페이스에 대한 요약입니다. 자세한 내용은 this previous post을 확인하십시오.




    상호 작용
    설명

    supplier()시작할 기본 개체를 제공합니다.
    accumulator()현재 스트리밍된 항목을 컨테이너에 누적하는 방법 설명
    combiner()스트림이 병렬인 경우 이를 병합하는 방법을 설명하십시오.
    finisher()변경 가능한 컨테이너 유형이 반환된 유형이 아닌 경우 전자를 후자로 변환하는 방법을 설명하십시오.
    characteristics()스트림 최적화를 위한 메타데이터 제공


    이를 감안할 때 그에 따라 Collector를 구현할 수 있습니다.

    private class PriceAndRowsCollector
        implements Collector<Map.Entry<Product, Integer>, PriceAndRows, PriceAndRows> {
    
        @Override
        public Supplier<PriceAndRows> supplier() {
            return PriceAndRows::new;                                                // 1
        }
    
        @Override
        public BiConsumer<PriceAndRows, Map.Entry<Product, Integer>> accumulator() {
            return (priceAndRows, entry) -> {                                        // 2
                var row = new CartRow(entry);
                priceAndRows.price = priceAndRows.price.add(row.getRowPrice());
                priceAndRows.rows.add(row);
            };
        }
    
        @Override
        public BinaryOperator<PriceAndRows> combiner() {
            return (c1, c2) -> {                                                     // 3
                c1.price = c1.price.add(c2.price);
                var rows = new ArrayList<>(c1.rows);
                rows.addAll(c2.rows);
                return new PriceAndRows(c1.price, rows);
            };
        }
    
        @Override
        public Function<PriceAndRows, PriceAndRows> finisher() {
            return Function.identity();                                              // 4
        }
    
        @Override
        public Set<Characteristics> characteristics() {
            return Set.of(Characteristics.IDENTITY_FINISH);                          // 4
        }
    }
    

  • 변경 가능한 컨테이너는 PriceAndRows 의 인스턴스입니다.
  • 제품 및 수량이 포함된 각 맵 항목에 대해 둘 다 PriceAndRows 에 누적됩니다.
  • 2개PriceAndRows는 총 가격을 합산하고 각각의 행을 집계하여 결합할 수 있습니다.
  • 변경 가능한 컨테이너를 있는 그대로 반환할 수 있습니다.
  • Collector를 설계하는 것은 약간 복잡하지만 사용자 지정 컬렉터를 사용하는 것은 다음과 같이 쉽습니다.

    var priceAndRows = cart.getProducts()
                           .entrySet()
                           .stream()
                           .collect(new PriceAndRowsCollector());
    

    결론


    Collectors 클래스에서 제공되는 즉시 사용 가능한 수집기 중 하나로 대부분의 사용 사례를 해결할 수 있습니다. 그러나 일부는 사용자 지정Collector을 구현해야 합니다(예: 단일 컬렉션 또는 단일 스칼라 이상을 수집해야 하는 경우).

    이전에 개발한 적이 없다면 복잡해 보일 수 있지만 그렇지 않습니다. 약간의 연습만 하면 됩니다. 이 게시물이 도움이 되길 바랍니다.

    Maven 형식의 GitHub에서 이 게시물의 소스 코드를 찾을 수 있습니다.


    아자바긱 / 커스텀 컬렉터






    더 나아가려면:
  • Custom collectors in Java 8
  • Collector Javadocs

  • 2021년 5월 2일 A Java Geek에서 원래 게시됨

    좋은 웹페이지 즐겨찾기