2017-02-22 51 views
7

У меня есть List<LedgerEntry> ledgerEntries, и мне нужно рассчитать суммы creditAmount и debitAmount.Java 8 Сумма двух свойств объекта за одну итерацию

class LedgerEntry{ 
private BigDecimal creditAmount; 
private BigDecimal debitAmount; 

//getters and setters 
} 

Я реализовал это, как,

BigDecimal creditTotal = ledgeredEntries.stream().map(p ->p.getCreditAmount()). 
reduce(BigDecimal.ZERO, BigDecimal::add); 
BigDecimal debitTotal = ledgeredEntries.stream().map(p ->p.getDebitAmount()). 
reduce(BigDecimal.ZERO, BigDecimal::add); 

//... 
//Use creditTotal, debitTotal later 

Это выглядит как я итерация в список дважды. Есть ли способ сделать это за один раз, не перепуская список дважды?

Pre Java 8 версия

BigDecimal creditTotal = BigDecimal.ZERO; 
BigDecimal debitTotal = BigDecimal.ZERO; 
for(LedgerEntry entry : ledgerEntries){ 
    creditTotal = creditTotal.add(entry.getCreditAmount()); 
    debitTotal = debitTotal.add(entry.getDebitAmount()); 
} 
+3

Почему вы хотите использовать потоки? Ваша версия «Pre Java 8» также на 100% действительна для Java 8 и (когда исправлено то, что она фактически ничего не делает, потому что «BigDecimal's являются неизменяемыми») более читабельными и поддерживаемыми (и, вероятно, более эффективными), чем любой поток решение, которое пытается рассчитать две суммы сразу. – Hoopje

+0

@KrazyKalle: Спасибо. сделал редактирование – Krishan

+0

@ KrazyKalle. Да. Как вы думаете, что я имел в виду с предложением между круглыми скобками (когда фиксировано ... неизменным)? – Hoopje

ответ

12

Вы могли бы свести к записи итоговых:

LedgerEntry totalsEntry = entries.stream().reduce(new LedgerEntry(), (te, e) -> { 
    te.setCreditAmount(te.getCreditAmount().add(e.getCreditAmount())); 
    te.setDebitAmount(te.getDebitAmount().add(e.getDebitAmount())); 

    return te; 
}); 

Update

В комментариях он был правильно указал, что reduce() не должен изменять первоначальный идентификатор значение, и что collect() следует использовать для изменяемых сокращений. Ниже приведена версия с использованием collect() (с использованием того же BiConsumer как для аккумулятора, так и для сумматора). Он также рассматривает вопрос о потенциальных NPE, если значения creditAmount и/или debitAmount не были установлены.

BiConsumer<LedgerEntry, LedgerEntry> ac = (e1, e2) -> { 
    BigDecimal creditAmount = e1.getCreditAmount() != null ? e1.getCreditAmount() : BigDecimal.ZERO; 
    BigDecimal debitAmount = e1.getDebitAmount() != null ? e1.getDebitAmount() : BigDecimal.ZERO; 

    e1.setCreditAmount(creditAmount.add(e2.getCreditAmount())); 
    e1.setDebitAmount(debitAmount.add(e2.getDebitAmount())); 
}; 

LedgerEntry totalsEntry = entries.stream().collect(LedgerEntry::new, ac, ac); 

Внезапно версия до Java 8 начинает казаться могущественной привлекательной.

+1

Может ли 'new LedgerEntry()' быть заменен на 'LedgerEntry :: new' для удобства чтения? – CKing

+3

@CKing No. Это начальное значение, а не ссылка на метод. –

+0

Отмечено. Я предположил, что 'сокращение' будет иметь перегруженную форму, которая использует функциональный интерфейс, такой как« Поставщик », но думаю, что такой вещи нет. – CKing

1

Вы должны обернуть свои результаты в Pair какой-то:

stream 
     .parallel() 
     .reduce(new AbstractMap.SimpleEntry<>(BigDecimal.ZERO, BigDecimal.ZERO), 
        (entry, ledger) -> { 
         BigDecimal credit = BigDecimal.ZERO.add(entry.getKey()).add(ledger.getCreditAmount()); 
         BigDecimal debit = BigDecimal.ZERO.add(entry.getValue()).add(ledger.getDebitAmount()); 
         return new AbstractMap.SimpleEntry<>(credit, debit); 
        }, (left, right) -> { 
         BigDecimal credit = BigDecimal.ZERO.add(left.getKey()).add(right.getKey()); 
         BigDecimal debit = BigDecimal.ZERO.add(left.getValue()).add(right.getValue()); 
         return new AbstractMap.SimpleEntry<>(credit, debit); 
        })); 

 Смежные вопросы

  • Нет связанных вопросов^_^