Ciro Anacleto Ciro Anacleto - 3 months ago 9
Java Question

How can I sum specific propertie from same object that are in different lists using Java 8 streams?

Using java 8 Streams API, how can I sum all values of specific propertie of equal items in different lists ?

For the example, I have the following code:

public class Streams {

static class PurchaseItemCancellation {
private Integer id;
private BigDecimal quantity;

// constructor
// getters and setters
}

static class PurchaseCancellation {
private Integer id;
private List<PurchaseItemCancellation> purchaseItemsCancellations;

// constructor
// getters and setters
}

public static void main (String ... args) {

PurchaseItemCancellation item1 = new PurchaseItemCancellation(1, new BigDecimal("10.00"));
PurchaseItemCancellation item2 = new PurchaseItemCancellation(2, new BigDecimal("20.00"));
PurchaseItemCancellation item3 = new PurchaseItemCancellation(3, new BigDecimal("30.00"));
PurchaseItemCancellation item4 = new PurchaseItemCancellation(4, new BigDecimal("40.00"));

PurchaseCancellation purchaseCancellation1 = new PurchaseCancellation(1, Arrays.asList(item1, item2));
PurchaseCancellation purchaseCancellation2 = new PurchaseCancellation(2, Arrays.asList(item3, item4));
PurchaseCancellation purchaseCancellation3 = new PurchaseCancellation(3, Arrays.asList(item4, item1));

List<PurchaseCancellation> cancellations = Arrays.asList(purchaseCancellation1, purchaseCancellation2, purchaseCancellation3);

final Comparator<PurchaseItemCancellation> byID = (p1, p2) -> Integer.compare(p1.getId(), p2.getId());

// List<PurchaseItemCancellation> itemsCancellations = cancellations.stream() ....

}
}


The
List<PurchaseCancellation> cancellations
has lists of cancellations on which each cancellation has a list of
PurchaseItemCancellation
;

So I need is sum all value from properties "quantity" of same item for each list of
PurchaseItemCancellation
contained on each cancellation.

The result must be a
List<PurchaseItemCancellation>
:

item1: id: 1, quantity: 20.00

item2: id: 2, quantity: 20.00

item3: id: 3, quantity: 30.00

item4: id: 4, quantity: 80.00

A executable code could be found in:

http://www.tutorialspoint.com/compile_java8_online.php?PID=0Bw_CjBb95KQMREE4TEJxa080NFE

P.S.: This site has two annoying bugs.

First bug: When you click the link is opened a file "HelloWorld.java".
Close this file and open the correct file :"Streams.java" on the left sidebar.
The execute button is on the top of editor.

Second bug: if the file is modified you must generate a new sharing link by clicking in
"Share code" button on the top of editor.
Sorry about this site. I don't know a better online java 8 code editor.

Any help will be apreciated.

Answer

The process outlined in the following code is this: take the stream of cancellations and convert it to a stream of PurchaseItemCancellation. Then we just perform a basic summation of that. (Edit I just noticed that for reasons that completely escape me, you are using BigDecimal values. That requires a slightly different approach.)

List<PurchaseItemCancellation> = cancellations.stream()
    .filter(c -> c != null)
    .map(PurchaseCancellation::getPurchaseItemCancellations)
    .filter(l -> l != null)
    .flatMap(List::stream)
    .collect(Collectors.groupingBy(PurchaseItemCancellation::getId,
        Collectors.reducing(BigDecimal.ZERO, PurchaseItemCancellation::getQuantity, BigDecimal::add)))
    .entrySet().stream()
    .map(e -> new PurchaseItemCancellation(e.getKey(), e.getValue()))
    .collect(Collectors.toList());

This is one line of code, but broken into the seven above lines it may be clearer what's going on.

  1. Convert to a Stream<PurchaseCancellation>
  2. Filter out the odd null cancellation - I don't know if this is a concern.
  3. Convert to a Stream<List<PurchaseItemCancellation>>
  4. Filter out any cancellations which had a null list - again, don't know if you have to worry about this.
  5. Convert to a Stream<PurchaseItemCancellation> - no longer grouped by which cancellation they came from
  6. Use a fancy converter which groups by id and within each group sums up the quantities. Now you have a Map<Integer, BigDecimal> from id to quantity
  7. Convert this map into another stream of the entries
  8. Convert it into another Stream<PurchaseItemCancellation>
  9. Get a list of this final result.

Sometimes it's easier not to use streams, y'know?