2014-10-23 7 views
9

Czy istnieje pewna mapa, w której można umieścić wszystkie swoje wpisy w jednej mapie?Czy istnieje elegancki sposób na ograniczenie strumienia map do jednej mapy?

Ignorowanie kwestie null, wpisy nadmiernie piśmie etc, co chciałbym kod jest:

public static <K, V> Map<K, V> reduce(Map<K, V>... maps) { 
    return Arrays.stream(maps) 
     .reduce(new HashMap<K, V>(), (a, b) -> a.putAll(b)); 
} 

ale daje to błąd kompilacji, ponieważ a.putAll(b) jest void. Jeśli wróci this, to będzie działać.

Aby obejść ten problem, mam zakodowane:

public static <K, V> Map<K, V> reduce(Map<K, V>... maps) { 
    return Arrays.stream(maps) 
     .reduce(new HashMap<K, V>(), (a, b) -> {a.putAll(b); return a;}); 
} 

który kompiluje i działa, ale jest brzydki lambda; kodowanie return a; wydaje się zbędne.

Jedno podejście byłaby na sposób jej przydatność

public static <K, V> Map<K, V> reduce(Map<K, V> a, Map<K, V> b) { 
    a.putAll(b); 
    return a; 
} 

który czyści lambda

public static <K, V> Map<K, V> reduce(Map<K, V>... maps) { 
    return Arrays.stream(maps) 
     .reduce(new HashMap<K, V>(), (a, b) -> reduce(a, b)); 
} 

lecz teraz ma, aczkolwiek nieco wielokrotnego użytku, sposób bezużyteczny użytkową.

Czy istnieje bardziej elegancki sposób wywoływania metody na akumulatorze i zwracania jej w zakresie lambda?

+4

Niestety sposób korzystania z 'reduce' jest zasadniczo pokruszone mogą pracować dla sekwencyjnego wykonania, lecz przerwa o wykonania równoległego. Dzieje się tak dlatego, że metoda reduce() traktuje swój pierwszy parametr (tożsamość) jako wartość do ponownego użycia. Działa więc dla typów pierwotnych i niezmiennych, ale zmienne wartości (np. 'Nowa HashMap <>()') na równoległych strumieniach będą zmutowane, prawdopodobnie równolegle, przez różne wątki. To się nie skończy. Odpowiedzią jest użycie 'collect()', tak jak w odpowiedzi @ Pshemo. –

Odpowiedz

13

reduce działa podobnie do

U result = identity; 
for (T element : this stream) 
    result = accumulator.apply(result, element) 
return result; 

co oznacza, że ​​N stanowiących accumulator.apply potrzeby do return związku (końcowe i pośrednie jeden).

Jeśli chcesz tego uniknąć zachowanie używać collect który działa bardziej jak

R result = supplier.get(); 
for (T element : this stream) 
    accumulator.accept(result, element); 
return result; 

tak lambda reprezentujący accumulator.accept nie trzeba return żadnej wartości, ale do modyfikacji result podstawie element.

przykład:

public static <K, V> Map<K, V> reduce(Map<K, V>... maps) { 
    return Arrays.stream(maps) 
      .collect(HashMap::new, Map::putAll, Map::putAll); 
      //      ^  ^
      //       |   collect results from parallel streams 
      //      collect results in single thread 
} 
+0

Niezłe wyjaśnienie. To uświadomiło mi istotną różnicę między zmniejszeniem a zbieraniem: zbieranie może być wykonywane równolegle, nie zmniejszaj (AFAICT). – Bohemian

+0

Dlaczego nie? Możesz wywołać 'reduce' na' pararelStream() 'jak na prostym' stream() '. Jest nawet [jedna przeciążona wersja dla niego] (http://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#reduce-U-java.util.function.BiFunction -java.util.function.BinaryOperator-), który ostatni argument umożliwia określenie sposobu zbierania wyników z równoległych strumieni. – Pshemo

+0

Zobacz mój komentarz powyżej. Nie możesz używać 'zmniejszenia' na zmiennym typie w strumieniach równoległych, dlatego też nie powinieneś używać go z sekwencyjnymi.Interfejs API nie jest jasny w tej kwestii, a wiele osób popełni ten błąd. Droga do wyjścia to "zbieranie". –