2015-10-21 23 views
15

Come è possibile eseguire quanto segue con i flussi Java?Stream Java: raggruppare un elenco in una mappa di Maps

diciamo che ho le seguenti classi:

class Foo { 
    Bar b; 
} 

class Bar { 
    String id; 
    String date; 
} 

Ho una List<Foo> e voglio convertirlo in un Map <Foo.b.id, Map<Foo.b.date, Foo>. I.e: gruppo prima dallo Foo.b.id e poi da Foo.b.date.

Sto lottando con il seguente approccio 2-step, ma il secondo non ha nemmeno la compilazione:

Map<String, List<Foo>> groupById = 
     myList 
       .stream() 
       .collect(
         Collectors.groupingBy(
           foo -> foo.getBar().getId() 
         ) 
       ); 

Map<String, Map<String, Foo>> output = groupById.entrySet() 
     .stream() 
     .map(
       entry -> entry.getKey(), 
       entry -> entry.getValue() 
         .stream() 
         .collect(
           Collectors.groupingBy(
             bar -> bar.getDate() 
           ) 
         ) 
     ); 

Grazie in anticipo.

+1

Bene, sei sicuro che ogni elemento nell'elenco sarà unico? Cioè, un id e una data daranno esattamente un singolo oggetto 'Foo'? – RealSkeptic

+1

Vuoi una 'Mappa ' o una 'Mappa >' ? – assylias

+0

@Eran hai ragione, editato :) – mrod

risposta

22

È possibile raggruppare i dati in un colpo solo assumendo ci sono solo distinti Foo:

Map<String, Map<String, Foo>> map = list.stream() 
     .collect(Collectors.groupingBy(f -> f.b.id, 
       Collectors.toMap(f -> f.b.date, Function.identity()))); 

risparmiare qualche chara cters utilizzando le importazioni statiche:

Map<String, Map<String, Foo>> map = list.stream() 
     .collect(groupingBy(f -> f.b.id, toMap(f -> f.b.date, identity()))); 
+1

Spot on, pal :) – mrod

2

Supponiamo che le coppie (b.id, b.date) siano distinte. Se è così, nel secondo passaggio non è necessario il raggruppamento, solo la raccolta di Map dove chiave è foo.b.date e il valore è foo stessa:

Map<String, Map<String, Foo>> map = 
     myList.stream() 
      .collect(Collectors.groupingBy(f -> f.b.id)) // map {Foo.b.id -> List<Foo>} 
      .entrySet().stream() 
      .collect(Collectors.toMap(e -> e.getKey(),     // id 
             e -> e.getValue().stream()  // stream of foos 
              .collect(Collectors.toMap(f -> f.b.date, 
                     f -> f)))); 

O ancora più semplice:

Map<String, Map<String, Foo>> map = 
     myList.stream() 
      .collect(Collectors.groupingBy(f -> f.b.id, 
              Collectors.toMap(f -> f.b.date, 
                  f -> f))); 
1

Un'alternativa è quella di sostenere il contratto uguaglianza sul tasto, Bar:

class Bar { 
    String id; 
    String date; 

    public boolean equals(Object o){ 
     if (o == null) return false; 
     if (!o.getClass().equals(getClass())) return false; 
     Bar other = (Bar)o; 
     return Objects.equals(o.id, id) && Objects.equals(o.date, date); 
    } 

    public int hashCode(){ 
     return id.hashCode*31 + date.hashCode; 
    }  
} 

Ora si può solo avere un Map<Bar, Foo>.

+0

OP vuole sicuramente 'Bar's con lo stesso' id' ma vengono memorizzati diversi 'date', ma nel tuo approccio essi sarà gettato via. Un altro difetto nel codice è il modo non sicuro di comparare 'id's--' Objects.equals() 'è molto meglio per questo. E non riesco a capire quale sia lo scopo di confrontare 'o.date' con' this.id' –

+0

@SashaSalauyou La prima affermazione non è vera, si possono avere gli stessi ID e date diverse usando questo metodo. E per quanto riguarda i null, Map non consente chiavi Null, quindi presumo che non ne abbiano. Il terzo punto è un errore di battitura, che probabilmente influisce anche sul primo punto! – weston

+0

Sì, la tua modifica ha reso l'uguaglianza basata su 'id' e' date', non solo su 'id'. Ora per cercare 'Foo' è necessario creare un fittizio oggetto' Bar' - sicuramente può essere un approccio. +1 –

Problemi correlati