2015-10-21 22 views
15

W jaki sposób mogę wykonać następujące czynności w przypadku strumieni Java?Strumienie Java: grupuj listę w mapę map

Powiedzmy mam następujące klasy:

class Foo { 
    Bar b; 
} 

class Bar { 
    String id; 
    String date; 
} 

Mam List<Foo> i chcę, aby przekonwertować go na Map <Foo.b.id, Map<Foo.b.date, Foo>. I.e: najpierw grupa przez Foo.b.id, a następnie przez Foo.b.date.

jestem zmaga się z następującym podejściu 2-step, ale drugi nie nawet skompilować:

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

Z góry dzięki.

+1

Czy na pewno każdy element na liście będzie niepowtarzalny? To znaczy, id i data da dokładnie jeden obiekt 'Foo'? – RealSkeptic

+1

Czy chcesz 'Mapa ' lub 'Mapa >' ? – assylias

+0

@Eran masz rację, edytuj :) – mrod

Odpowiedz

22

Można grupa twoje dane w jednym zamachem zakładając, że są tylko wyraźną Foo:

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

Saving niektóre chara cters przy użyciu importu statyczne:

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

Załóżmy, że pary są różne. Jeśli tak, w drugim etapie nie trzeba grupowania, tylko zbieranie do Map gdzie klucz jest foo.b.date i wartość jest foo sama:

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

Albo jeszcze bardziej prosta:

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

Alternatywą jest wspieranie równości kontrakt na klucz, 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; 
    }  
} 

Teraz może po prostu masz Map<Bar, Foo>.

+0

OP zdecydowanie chce, żeby 'Bar's z tym samym' identyfikatorem' ale inną 'date' również były przechowywane, ale w twoim podejściu zostanie wyrzucony. Kolejna luka w twoim kodzie jest zerowym niebezpiecznym sposobem porównywania 'id's -' Objects.equals() 'jest o wiele lepsza. I nie mogę zrozumieć, jaki jest cel porównania 'o.date' z' this.id'. –

+0

@SashaSalauyou Pierwsze stwierdzenie nie jest prawdziwe, możesz mieć te same identyfikatory i różne daty, używając tej metody. A jeśli chodzi o wartości zerowe, Map nie zezwala na klawisze zerowe, więc zakładam, że nie mają żadnych. Trzeci punkt to literówka, która prawdopodobnie dotyczy także pierwszego punktu! – weston

+0

Tak, twoja zmiana wprowadziła równość na podstawie "id" i "date", a nie "id". Teraz, aby wyszukać 'Foo', musisz utworzyć atrapę' Bar' obiektu - to z pewnością może być podejście. +1 –

Powiązane problemy