2015-09-17 3 views
9

Sto passando da orribili nidificati per loop a espressioni lambda disegnate in java.Lambda Raccogliere gli elementi creati all'interno di un metodo di consumo

Ecco il mio codice vero e proprio

for (String foo : foos) { 
    for (Bar bar : bars) {  
     if (bar.getFoo().equals(foo)) { 
      FooBar fooBar = new FooBar();       
      fooBar.setBar(bar); 
      listOfFooBar.add(fooBar); 
      break; 
     } 
    } 
} 

Il mio codice lambda effettivo per sostituire il codice sopra

foos.forEach(i -> bars.stream().filter(p -> p.getFoo().equals(i)).findFirst().ifPresent(p -> { 
     FooBar s = new FooBar(); 
     fooBar.setBar(bar); 
     listOfFooBar.add(fooBar); 
    })); 

La mia domanda è, c'è un modo per popolare listOfFooBar con qualche tipo di raccolta() metodo?

Qualcosa di simile listOfFooBar = foos.forEach(.....).collect(Collectors.toList());

Un fatto è che le barre conterrà sempre tutti i foo, Foos è fondamentalmente una piccola parte di bar.

Se c'è un modo migliore (in termini di prestazioni o eleganza) di fare quel lambda, si prega di condividere.

+1

Penso che dovrebbe essere 'bar.getFoo()' invece di 'bar.getBar()', corretto? –

+1

@FedericoPeraltaSchaffner sì, questo ha più senso :) –

+2

Perché mai tutti cercano di fare tutto con 'forEach' prima? 'Stream' ha più metodi di quello. – Holger

risposta

5

Poiché v'è un solo bar per Foo, si potrebbe iniziare con la creazione di una mappa che collega Foos Bars:

Map<String, Bar> barsByFoo = bars.stream().collect(toMap(Bar::getFoo, b -> b)); 

Se avete un sacco più barre di foos, è possibile filtrare:

Map<String, Bar> barsByFoo = bars.stream() 
           .filter(b -> foos.contains(b.getFoo())) 
           .collect(toMap(Bar::getFoo, b -> b)); 

vostro annidati per i loop possono poi essere scritti:

List<FooBar> listOfFooBar = foos.stream() 
     .map(barsByFoo::get) 
     .filter(Objects::nonNull) 
     .map(FooBar::new) 
     .collect(toList()); 

Questo presuppone che ci sia un costruttore FooBar(Bar).

Oppure si potrebbe prendere il problema dal lato opposto e utilizzare un (credo) algo equivalente (si sarebbe probabilmente trarre vantaggio dall'utilizzo di un Set<Foo> in quel caso):

List<FooBar> listOfFooBar = bars.stream() 
     .filter(bar -> foos.contains(bar.getFoo())) 
     .map(FooBar::new) 
     .collect(toList()); 

In entrambi i casi, aiuta in generale fare un passo indietro dal ciclo iniziale poiché un diverso approccio/algo è generalmente vantaggioso per una soluzione lambda pulita.

+0

Sì, c'è solo una barretta per Foo. Sto testando il tuo codice adesso –

+1

@JohnnyWiller Ho aggiornato di conseguenza – assylias

+0

'toMap' undefined? –

4

Se si vuole andare il tutto nove cantieri:

List<FooBar> listOfFooBar = foos.stream() 
    .flatMap(foo -> bars.stream().filter(bar-> bar.getFoo().equals(foo)).findFirst() 
        .map(Stream::of).orElse(Stream.empty())) 
    .map(bar -> { 
       FooBar fooBar = new FooBar(); 
       fooBar.setBar(bar); 
       return fooBar; 
       }) 
    .collect(Collectors.toList()); 

Se tu avessi un costruttore FooBar che accetta un Bar allora si potrebbe risparmiare un po 'le linee e scrivere

.map(FooBar::new) 

FWIW in Java 9 sarai in grado di scrivere

.findFirst().stream() 

Supponendo che c onstructor sarebbe quindi ridurre al

List<FooBar> listOfFooBar = foos.stream() 
    .flatMap(foo -> bars.stream().filter(bar-> bar.getFoo().equals(foo)).findFirst().stream())) 
    .map(FooBar::new) 
    .collect(Collectors.toList()); 

EDIT: Utilizzando @ suggerimento di Misha è possibile ridurre ancora di più:

List<FooBar> listOfFooBar = foos.stream() 
    .flatMap(foo -> bars.stream().filter(bar-> bar.getFoo().equals(foo)).limit(1))) 
    .map(FooBar::new) 
    .collect(Collectors.toList()); 
+3

Puoi sostituire '.findFirst(). Map (Stream :: of) .orElse (Stream.empty())' con '.limit (1)' – Misha

0

Se FooBar ha un costruttore che accetta un Bar come argomento:

public class FooBar { 

    public FooBar(Bar bar) { 
     // do something with bar, assign it, etc 
    } 
} 

Quindi, è possibile farlo come segue:

List<FooBar> fooBars = foos.stream() 
    .map(foo -> bars.stream() 
     .filter(bar -> bar.getFoo().equals(foo)) 
     .findFirst() 
     .map(FooBar::new)) 
    .filter(Optional::isPresent) 
    .map(Optional::get) 
    .collect(Collectors.toList()); 

questo torrenti tua foos, e per ogni foo, è flussi tua bars finché non trova il primo che corrisponde al corrente foo. Se viene rilevato un valore foo, viene creato un nuovo FooBar dall'attuale bar corrente del flusso interno. Questo ci lascia con un flusso di Optional<FooBar>, che viene quindi filtrato per mantenere solo gli optionals non vuoti. Quindi, gli optionals vengono trasformati nei valori che contengono (che sono i FooBar s creati nel passaggio precedente) e infine, questi FooBar vengono raccolti in un List<FooBar>.

MODIFICA: Questo è stato il mio primo tentativo.Molto meglio usare @ approccio di zeroflagL:

List<FooBar> fooBars = foos.stream() 
    .flatMap(foo -> bars.stream() 
     .filter(bar -> bar.getFoo().equals(foo)) 
     .findFirst() 
     .map(Stream::of).orElse(Stream.empty())) 
    .map(FooBar::new) 
    .collect(Collectors.toList());