2014-07-04 16 views
5

Ho la seguente classe:Lista Aggregate <X> to List <X> con Java 8 API flusso

class Money { 
    CurrencyUnit currencyUnit; 
    BigDecimal amount; 
} 

Nella mia applicazione, ottengo qualche lista casuale di oggetti Money:

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

Ora Mi piacerebbe utilizzare l'API Java 8 Stream per creare il seguente risultato (chiamando semplicemente BigDecimal::add per ogni importo di valutaUnit):

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

quello che già so/DID:

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

E qui finisce già. So che posso utilizzare un Collector per produrre un Map<CurrencyUnit, List<Money>>:

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

Ma poi devo ancora passare attraverso tutti valore di coppie di chiavi e riassumere la quantità di dati.

Qual è il giusto (e forse il modo più semplice) per farlo? Non può essere così complicato, giusto? :)


EDIT: Se non è chiaro quello che mi serve, ecco il mio vecchio modo di codice Java:

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: Un'altra soluzione, che non è davvero elegante:

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

risposta

10

È possibile utilizzare un collezionista groupingBy per raggruppare gli oggetti da CurrencyUnit. Senza secondi argomenti, il metodo groupingBy raccoglie gli elementi in un elenco. Tuttavia, se hai bisogno di qualcos'altro, puoi anche specificare un collettore a valle .

È possibile utilizzare Collectors::summingInt e Collectors::summingLong per int e long. Per BigDecimal, si può ripiegare a Collectors::reducing:

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: È inoltre possibile creare List<Money>:

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

Grazie. È vicino a ciò di cui ho bisogno, ma il tuo codice mi dà una 'Mappa'. Non è possibile recuperare un 'List' o un' Set'? (senza creare manualmente nuovi oggetti 'Money', ecc.) –

+0

Solo 5 minuti fa mi è venuta in mente l'idea di scorrere l'entryset, ma ho pensato: è davvero brutto, deve esserci qualcosa di più sexy/elegante. '---' Ma penso che la tua risposta data sia l'unico modo per farlo. '---' proprio ora pensavo a: 'moneyStream.collect (Collectors.groupingBy (m -> m.getCurrencyUnit());' e quindi usa la 'Mappa Map CurrencyUnit, List >' con 'map. values ​​(). stream() 'per produrre un' Elenco '?! –

Problemi correlati