2014-07-04 2 views
5

ich die folgende Klasse haben:Aggregate Liste <X><X> mit Java 8 Stream-API zur Liste

class Money { 
    CurrencyUnit currencyUnit; 
    BigDecimal amount; 
} 

In meiner Anwendung, erhalte ich eine zufällige Liste von Money Objekte:

currencyUnit | amount 
--------------------- 
EUR   | 5.1 
EUR   | 0 
USD   | 1.09 
EUR   | 42 
USD   | 3 

Jetzt Ich möchte die Java 8 Stream API verwenden, um das folgende Ergebnis zu erstellen (einfach BigDecimal::add für jede Währung Unit aufrufen):

currencyUnit | amount 
--------------------- 
EUR   | 47.1 
USD   | 4.09 

Was ich schon weiß/tat

Stream<Money> moneyStream = moneyList.stream(); 

Und hier endet es bereits. Ich weiß, ich kann eine Collector verwenden ein Map<CurrencyUnit, List<Money>> zu produzieren:

moneyStream.collect(Collectors.groupingBy(m -> m.getCurrencyUnit()); 

Aber dann gehe ich muss noch durch alle Schlüssel-Wert-Paare und die Menge Daten zusammenzufassen.

Was ist der richtige (und möglicherweise einfachste Weg), das zu tun? Es kann nicht so kompliziert sein, oder? :)


EDIT: Wenn es nicht so klar ist, was ich brauche, hier ist meine alte Art und Weise Java-Code:

Map<CurrencyUnit, Money> map = new HashMap<>(); 
moneyList.stream().forEach(e -> { 
    Money m = map.get(e.getCurrencyUnit()); 
    if(m == null) { 
     m = new Money(); 
     m.setAmount(BigDecimal.ZERO); 
     m.setCurrencyUnit(e.getCurrencyUnit()); 
     map.put(e.getCurrencyUnit(), m); 
    } 
    m.setAmount(m.getAmount().add(e.getAmount())); 
}); 
return map.values(); 

EDIT 2: Eine andere Lösung, die nicht wirklich elegant ist:

List<Money> list = inputList.stream() 
    .collect(Collectors.groupingBy(Money::getCurrencyUnit)) 
    .values().stream().map(ml -> { 
     Money money = new Money(); 
     ml.forEach(m -> { 
      if(money.getCurrencyUnit() == null) { 
       money.setCurrencyUnit(m.getCurrencyUnit()); 
       money.setAmount(m.getAmount()); 
      } else { 
       money.setAmount(money.getAmount().add(m.getAmount())); 
      } 
     }); 
     return money; 
    }).collect(Collectors.toList()); 

Antwort

10

Sie können CurrencyUnit die Objekte eine groupingBy Sammler zu Gruppe verwenden. Ohne zweites Argument sammelt die groupingBy Methode die Elemente in einer Liste. Sie können jedoch auch einen Downstream-Collector angeben, wenn Sie etwas anderes benötigen.

Sie können Collectors::summingInt und Collectors::summingLong für int und long verwenden. Für BigDecimal, können Sie zurück zu Collectors::reducing fallen:

import static java.util.stream.Collectors.groupingBy; 
import static java.util.stream.Collectors.reducing; 

Map<CurrencyUnit, BigDecimal> result = moneyList.stream() 
    .collect(
     groupingBy(
      Money::getCurrencyUnit, 
      reducing(BigDecimal.ZERO, Money::getAmount, BigDecimal::add))); 

Edit: Sie auch List<Money> erstellen:

List<Money> result = moneyList.stream() 
    .collect(
     groupingBy(
      Money::getCurrencyUnit, 
      reducing(BigDecimal.ZERO, Money::getAmount, BigDecimal::add))) 
    .entrySet().stream() 
    .map(e -> new Money(e.getKey(), e.getValue()) 
    .collect(toList()); 
+0

Danke. Das ist nah an dem, was ich brauche, aber Ihr Code gibt mir eine 'Map'. Ist es nicht möglich, eine "Liste" oder ein "Set" zurück zu bekommen? (ohne manuell neue 'Money'-Objekte zu erstellen, etc.) –

+0

Vor gerade einmal 5 Minuten kam mir die Idee, über den EintragSet zu denken, in den Sinn, aber ich dachte: Das ist wirklich hässlich, da muss etwas mehr sexy/elegant sein. »Aber ich denke, deine gegebene Antwort ist der einzige Weg, das zu tun. '---' genau jetzt dachte ich über: 'moneyStream.collect (Collectors.groupingBy (m -> m.getCurrencyUnit());' und dann benutze die generierte 'Map >' mit 'map. values ​​(). stream() 'um eine' List ''! –