2015-05-29 15 views
16

Korzystanie z Java 8 lambdas, jaki jest "najlepszy" sposób, aby skutecznie utworzyć nowy List<T>, biorąc pod uwagę List<K> możliwych kluczy i Map<K,V>? Jest to scenariusz, w którym podano List z możliwych kluczy Map i oczekuje się, że wygenerują one List<T>, gdzie T jest typem, który jest skonstruowany na podstawie pewnego aspektu V, typów wartości mapy.Jak utworzyć listę <T> z mapy <K,V> i listę <K> kluczy?

Znalazłem kilka i nie czuję się dobrze, twierdząc, że jeden sposób jest lepszy od drugiego (z może jednym wyjątkiem - patrz kod). Wyjaśnię "najlepszy" jako kombinację klarowności kodu i wydajności środowiska wykonawczego. Oto, co wymyśliłem. Jestem pewien, że ktoś może zrobić lepiej, co jest jednym aspektem tego pytania. Nie podoba mi się aspekt filter większości, ponieważ oznacza to konieczność tworzenia struktur pośrednich i wielokrotnych przejść przez nazwy List. W tej chwili wybieram przykład 6 - zwykłą "pętlę". (UWAGA: Niektóre tajemnicze myśli są w komentarzach kodu, szczególnie „trzeba odwołać zewnątrz ...” Oznacza to zewnętrzny z lambda.)

public class Java8Mapping { 
    private final Map<String,Wongo> nameToWongoMap = new HashMap<>(); 
    public Java8Mapping(){ 
     List<String> names = Arrays.asList("abbey","normal","hans","delbrook"); 
     List<String> types = Arrays.asList("crazy","boring","shocking","dead"); 
     for(int i=0; i<names.size(); i++){ 
      nameToWongoMap.put(names.get(i),new Wongo(names.get(i),types.get(i))); 
     } 
    } 

    public static void main(String[] args) { 
     System.out.println("in main"); 
     Java8Mapping j = new Java8Mapping(); 
     List<String> testNames = Arrays.asList("abbey", "froderick","igor"); 
     System.out.println(j.getBongosExample1(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", "))); 
     System.out.println(j.getBongosExample2(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", "))); 
     System.out.println(j.getBongosExample3(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", "))); 
     System.out.println(j.getBongosExample4(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", "))); 
     System.out.println(j.getBongosExample5(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", "))); 
     System.out.println(j.getBongosExample6(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", "))); 
    } 

    private static class Wongo{ 
     String name; 
     String type; 
     public Wongo(String s, String t){name=s;type=t;} 
     @Override public String toString(){return "Wongo{name="+name+", type="+type+"}";} 
    } 

    private static class Bongo{ 
     Wongo wongo; 
     public Bongo(Wongo w){wongo = w;} 
     @Override public String toString(){ return "Bongo{wongo="+wongo+"}";} 
    } 

    // 1: Create a list externally and add items inside 'forEach'. 
    //  Needs to externally reference Map and List 
    public List<Bongo> getBongosExample1(List<String> names){ 
     final List<Bongo> listOne = new ArrayList<>(); 
     names.forEach(s -> { 
        Wongo w = nameToWongoMap.get(s); 
        if(w != null) { 
         listOne.add(new Bongo(nameToWongoMap.get(s))); 
        } 
       }); 
     return listOne; 
    } 

    // 2: Use stream().map().collect() 
    // Needs to externally reference Map 
    public List<Bongo> getBongosExample2(List<String> names){ 
     return names.stream() 
       .filter(s -> nameToWongoMap.get(s) != null) 
       .map(s -> new Bongo(nameToWongoMap.get(s))) 
       .collect(Collectors.toList()); 
    } 

    // 3: Create custom Collector 
    // Needs to externally reference Map 
    public List<Bongo> getBongosExample3(List<String> names){ 
     Function<List<Wongo>,List<Bongo>> finisher = list -> list.stream().map(Bongo::new).collect(Collectors.toList()); 
     Collector<String,List<Wongo>,List<Bongo>> bongoCollector = 
       Collector.of(ArrayList::new,getAccumulator(),getCombiner(),finisher, Characteristics.UNORDERED); 

     return names.stream().collect(bongoCollector); 
    } 
    // example 3 helper code 
    private BiConsumer<List<Wongo>,String> getAccumulator(){ 
     return (list,string) -> { 
      Wongo w = nameToWongoMap.get(string); 
      if(w != null){ 
       list.add(w); 
      } 
     }; 
    } 
    // example 3 helper code 
    private BinaryOperator<List<Wongo>> getCombiner(){ 
     return (l1,l2) -> { 
      l1.addAll(l2); 
      return l1; 
     }; 
    } 

    // 4: Use internal Bongo creation facility 
    public List<Bongo> getBongosExample4(List<String> names){ 
     return names.stream().filter(s->nameToWongoMap.get(s) != null).map(s-> new Bongo(nameToWongoMap.get(s))).collect(Collectors.toList()); 
    } 

    // 5: Stream the Map EntrySet. This avoids referring to anything outside of the stream, 
    // but bypasses the lookup benefit from Map. 
    public List<Bongo> getBongosExample5(List<String> names){ 
     return nameToWongoMap.entrySet().stream().filter(e->names.contains(e.getKey())).map(e -> new Bongo(e.getValue())).collect(Collectors.toList()); 
    } 

    // 6: Plain-ol-java loop 
    public List<Bongo> getBongosExample6(List<String> names){ 
     List<Bongo> bongos = new ArrayList<>(); 
     for(String s : names){ 
      Wongo w = nameToWongoMap.get(s); 
      if(w != null){ 
       bongos.add(new Bongo(w)); 
      } 
     } 
     return bongos; 
    } 
} 
+1

Masz 'K' i' V', ale co to jest 'T'? – user2357112

+0

@ user2357112 Edytowano. T jest typem, który nie znajduje się na mapie, ale jest konstruowany przy użyciu wartości mapy. Nadzieja, która pomaga. – MadConan

+3

plain-ol-java, aby wygrać! – ZhongYu

Odpowiedz

11

Jeśli namesToWongoMap jest zmienną instancji, nie można naprawdę unikaj przechwytywania lambda.

można oczyścić strumieniem przez dzielenie się operacje trochę więcej:

return names.stream() 
    .map(n -> namesToWongoMap.get(n)) 
    .filter(w -> w != null) 
    .map(w -> new Bongo(w)) 
    .collect(toList()); 
return names.stream() 
    .map(namesToWongoMap::get) 
    .filter(Objects::nonNull) 
    .map(Bongo::new) 
    .collect(toList()); 

W ten sposób nie nazywamy get dwukrotnie.

To jest bardzo podobne do pętli for, z wyjątkiem, na przykład, teoretycznie można zrównoleglić, jeśli namesToWongoMap nie może być zmutowane jednocześnie.

nie lubią filter aspekt najbardziej jak to znaczy konieczności tworzenia struktur pośrednich i wiele przechodzi nazwami List.

Brak pośrednich konstrukcji i jest tylko jedno przejście przez List. Potok potokowy mówi "dla każdego elementu ... wykonaj tę sekwencję operacji". Każdy element jest odwiedzany raz, a potok jest stosowany.

Oto kilka istotnych cytatów z java.util.stream package description:

Strumień nie jest strukturą danych, która przechowuje elementy; zamiast tego przenosi elementy ze źródła, takiego jak struktura danych, macierz, funkcja generatora lub kanał I/O, poprzez potok operacji obliczeniowych.

Przetwarzanie strumieni leniwie pozwala na znaczącą poprawę wydajności; w potoku, takim jak powyższy przykład filtra-mapy-sumy, filtrowanie, mapowanie i zsumowanie mogą być połączone w jedno przejście danych, z minimalnym stanem pośrednim.

+0

Nice. Prosto i czysto. Lubię to! – MadConan

+1

Myślę, że będę siedział na tym przez dzień lub dwa i widziałem innych dzwonków. Chciałbym zobaczyć, czy ktoś może wymyślić coś lepszego, choć wątpię w to. :) – MadConan

3

Jedno podejście nie widzę jest retainAll:

public List<Bongo> getBongos(List<String> names) { 
    Map<String, Wongo> copy = new HashMap<>(nameToWongoMap); 
    copy.keySet().retainAll(names); 

    return copy.values().stream().map(Bongo::new).collect(
     Collectors.toList()); 
} 

Dodatkowa mapa jest minimalny spadek wydajności, ponieważ jest po prostu skopiowanie wskaźniki do obiektów, a nie same obiekty.

+1

Weeelllll ... Myślę, że istnieje * względnie * duża ilość narzutów związanych z tworzeniem wszystkich węzłów dla każdego wpisu. Wychodzenie z [tego starszego artykułu] (http://www.javacodegeeks.com/2010/08/java-best-practices-vector-arraylist.html). Ale twoja odpowiedź, jeśli jest ładna i czysta, a koszty ogólne mogą nie być czynnikiem. – MadConan

7

Radiodef's answer dość mocno przybity to, myślę. Rozwiązanie podane tam:

return names.stream() 
    .map(namesToWongoMap::get) 
    .filter(Objects::nonNull) 
    .map(Bongo::new) 
    .collect(toList()); 

jest prawdopodobnie o najlepsze, co można zrobić w Javie 8.

ja chciałem wspomnieć małą zmarszczkę w tym, choć. Wywołanie Map.get zwraca null, jeśli nazwa nie jest obecna na mapie, a następnie jest odfiltrowywana. Nie ma nic złego w tym per se, chociaż on wypieka semantyczną null-means-not-present w strukturę potoku.

W pewnym sensie chcielibyśmy, aby operacja potokowania mapperów miała wybór zwracania zera lub jednego elementu. Sposobem na zrobienie tego ze strumieniami jest flatMap. Funkcja flatmapper może zwrócić dowolną liczbę elementów do strumienia, ale w tym przypadku chcemy mieć zero lub jeden. Oto jak to zrobić:

return names.stream() 
    .flatMap(name -> { 
     Wongo w = nameToWongoMap.get(name); 
     return w == null ? Stream.empty() : Stream.of(w); 
    }) 
    .map(Bongo::new) 
    .collect(toList()); 

Przyznaję to dość niezgrabne i tak nie polecam tego robić. Nieco lepsze, ale nieco niejasne podejście to:

return names.stream() 
    .flatMap(name -> Optional.ofNullable(nameToWongoMap.get(name)) 
          .map(Stream::of).orElseGet(Stream::empty)) 
    .map(Bongo::new) 
    .collect(toList()); 

, ale nadal nie jestem pewien, czy zaleciłbym to w obecnej formie.

Zastosowanie metody flatMap wskazuje jednak na inne podejście. Jeśli masz bardziej skomplikowaną zasadę radzenia sobie z nieistniejącym przypadkiem, możesz przekształcić to w funkcję pomocniczą, która zwraca Strumień zawierający wynik lub pusty Strumień, jeśli nie ma wyniku.

Wreszcie JDK 9 - wciąż w fazie rozwoju, jak to pisze - dodała Stream.ofNullable która jest przydatna w dokładnie takich sytuacjach:

return names.stream() 
    .flatMap(name -> Stream.ofNullable(nameToWongoMap.get(name))) 
    .map(Bongo::new) 
    .collect(toList()); 

Tak na marginesie, JDK 9 dodał również Optional.stream który tworzy strumień zerowy lub jeden z Optional. Jest to przydatne w przypadkach, w których chcesz wywołać funkcję Zwrot opcjonalny z poziomu flatMap. Aby uzyskać więcej informacji, patrz: this answer i this answer.

+2

Dodałem już [StreamEx.ofNullable (obj)] (http://amaembo.github.io/streamex/javadoc/javax/util/streamex/StreamEx.html#ofNullable-T-) i [StreamEx.of (opcjonalnie) ] (http: //amaembo.github.io/streamex/javadoc/javax/util/streamex/StreamEx.html # of-java.util.Optional-) do mojej biblioteki. W rzeczywistości nawet bez bibliotek stron trzecich i przechodzenia do JDK9 każdy może tworzyć podobne metody statyczne w niektórych klasach narzędzi specyficznych dla projektu i używać ich. –

Powiązane problemy