2015-08-03 5 views
7

Se voglio un elenco totale dei saldi attuali conti, non posso fare:Java lambda per restituire null se lista vuota altrimenti somma di valori?

accountOverview.setCurrentBalance(account.stream(). 
       filter(a -> a.getCurrentBalance() != null). 
       mapToLong(a -> a.getCurrentBalance()). 
       sum()); 

Ma questa espressione tornerà 0, anche se tutti i saldi sono nulli. Mi piacerebbe restituire null se tutti i saldi sono nulli, 0 se ci sono saldi 0 non nulli e altrimenti la somma dei saldi.

Come posso farlo con un'espressione lambda?

Molte grazie

+2

È meglio suddividerlo in 2 righe, una per filtrare il 'nullo' e un altro per restituire 'null' se la dimensione è' 0', somma altrimenti. È possibile farlo in una riga usando 'reduce' piuttosto che' filtro', ma la leggibilità ne risentirebbe. –

+4

In realtà è una cattiva pratica restituire null (vedi NullObject Pattern) ... – Maksym

+0

@pbabcdefp - ah, grazie. Sì, l'ho rielaborato in due righe come suggerisci, ed è sicuramente più leggibile. – user384842

risposta

3

Per ora, sto andando con questo. Pensieri?

 accountOverview.setCurrentBalance(account.stream(). 
       filter(a -> a.getCurrentBalance() != null). 
       map(a -> a.getCurrentBalance()). 
       reduce(null, (i,j) -> { if (i == null) { return j; } else { return i+j; } })); 

Poiché ho già filtrato i valori null, sono sicuro di non averne alcuno. Facendo in modo che il param iniziale riduca "null", posso assicurarmi di ottenere null su una lista vuota.

Si sente un po 'difficile/confuso da leggere però. Vorrebbe una soluzione più bello ..

EDIT Grazie a pbabcdefp, sono andato con questa soluzione un po 'più rispettabile:

 List<Account> filtered = account.stream(). 
       filter(a -> a.getCurrentBalance() != null). 
       collect(Collectors.toList()); 

     accountOverview.setCurrentBalance(filtered.size() == 0?null: 
      filtered.stream().mapToLong(a -> a.getCurrentBalance()). 
      sum()); 
2

Stai cercando di fare due cose fondamentalmente contraddicendo: filtrare elementi nulli (che è un'operazione locale, basata su un singolo elemento) e rileva quando tutti gli elementi sono nulli (che è un'operazione globale, basata sull'intero elenco). Normalmente dovresti farlo come due operazioni separate, il che rende le cose molto più leggibili.

A parte il reduce() trucco che hai già trovato, è anche possibile ricorrere a trucchi subdoli, se si sa che l'equilibrio non può mai essere negativo, per esempio, si può fare qualcosa di simile

long sum = account.stream(). 
       mapToLong(a -> a.getCurrentBalance() == null ? 0 : a.getCurrentBalance()+1). 
       sum() - account.size(); 
Long nullableSum = sum < 0 ? null : sum; 

Ma si' Devo chiedertelo: è ciò che guadagni solo iterando attraverso la tua collezione, una volta che vale il costo di aver scritto un pezzo di codice illeggibile e abbastanza fragile? Nella maggior parte dei casi la risposta sarà: no.

5

Una volta filtrati dallo stream, non c'è modo di sapere se tutti i saldi sono stati null (a meno che non verifichi cosa restituisce count() ma non sarà possibile utilizzare lo streaming poiché si tratta di un'operazione terminale).

Facendo due passaggi sui dati è probabilmente la soluzione straight-forward, e mi sarebbe probabilmente andare con quel primo:

boolean allNulls = account.stream().map(Account::getBalance).allMatch(Objects::isNull); 

Long sum = allNulls ? null : account.stream().map(Account::getBalance).filter(Objects::nonNull).mapToLong(l -> l).sum(); 

Si potrebbe eliminare il passaggio filtrante con la vostra soluzione con reduce, anche se il leggibilità forse non essere il migliore:

Long sum = account.stream() 
        .reduce(null, (l1, l2) -> l1 == null ? l2 : 
                 l2 == null ? l1 : Long.valueOf(l1 + l2)); 

Avviso del Long.valueOf chiamata. È per evitare che il tipo di espressione condizionale sia long e quindi un NPE su alcuni casi limite.


Un'altra soluzione sarebbe utilizzare l'API Optional.In primo luogo, creare una Stream<Optional<Long>> dai valori dei saldi e ridurli:

Optional<Long> opt = account.stream() 
          .map(Account::getBalance) 
          .flatMap(l -> Stream.of(Optional.ofNullable(l))) 
          .reduce(Optional.empty(), 
            (o1, o2) -> o1.isPresent() ? o1.map(l -> l + o2.orElse(0L)) : o2); 

Questo vi darà un Optional<Long> che sarà vuota se tutti i valori erano null, altrimenti ti do la somma della non valori null.

Oppure si potrebbe desiderare di creare una collezione personalizzata per questo:

class SumIntoOptional { 

    private boolean allNull = true; 
    private long sum = 0L; 

    public SumIntoOptional() {} 

    public void add(Long value) { 
     if(value != null) { 
      allNull = false; 
      sum += value; 
     } 
    } 

    public void merge(SumIntoOptional other) { 
     if(!other.allNull) { 
      allNull = false; 
      sum += other.sum; 
     } 
    } 

    public OptionalLong getSum() { 
     return allNull ? OptionalLong.empty() : OptionalLong.of(sum); 
    } 
} 

e poi:

OptionalLong opt = account.stream().map(Account::getBalance).collect(SumIntoOptional::new, SumIntoOptional::add, SumIntoOptional::merge).getSum(); 


Come si può vedere, ci sono vari modi per raggiungere questo obiettivo, quindi la mia consiglio sarebbe: scegliere prima il più leggibile. Se si verificano problemi di prestazioni con la soluzione, controllare se potrebbe essere migliorata (sia ruotando il flusso in parallelo o utilizzando un'altra alternativa). Ma misura, non indovinare.

Problemi correlati