Zrozumieć Spliterator, Collector i Stream w Javie 8

143

Mam problem ze zrozumieniem Streaminterfejsu w Javie 8, zwłaszcza gdy ma on związek z interfejsami Spliteratori Collector. Mój problem polega na tym, że po prostu nie rozumiem Spliteratori Collectorinterfejsów jeszcze, w wyniku czego Streaminterfejs jest nadal dla mnie nieco niejasny.

Czym dokładnie jest a Spliteratori a Collectori jak ich używać? Jeśli chcę napisać własne Spliteratorlub Collector(i prawdopodobnie moje Streamw tym procesie), co powinienem zrobić, a czego nie?

Czytałem kilka przykładów rozsianych po sieci, ale ponieważ wszystko tutaj jest wciąż nowe i podlega zmianom, przykłady i samouczki są nadal bardzo rzadkie.

Victor Stafusa
źródło

Odpowiedzi:

142

Prawie na pewno nigdy nie powinieneś mieć do czynienia z Spliteratorużytkownikiem; powinno być konieczne tylko wtedy, gdy piszesz Collectionrodzaje siebie i również zamierza celu optymalizacji parallelized operacje na nich.

Co jest warte, a Spliterator to sposób na operowanie elementami kolekcji w taki sposób, że łatwo jest oddzielić część kolekcji, np. Ponieważ robisz równolegle i chcesz, aby jeden wątek pracował na jednej części kolekcji, jeden wątek do pracy na innej części itp.

Zasadniczo nie powinieneś także zapisywać wartości typu Streamdo zmiennej. Streamjest czymś w rodzaju znaku Iterator, ponieważ jest to obiekt jednorazowego użytku, którego prawie zawsze będziesz używać w płynnym łańcuchu, jak w przykładzie Javadoc:

int sum = widgets.stream()
                  .filter(w -> w.getColor() == RED)
                  .mapToInt(w -> w.getWeight())
                  .sum();

Collectorjest najbardziej uogólnioną, abstrakcyjną możliwą wersją operacji „redukuj” a la map / redukuj; w szczególności musi wspierać etapy zrównoleglania i finalizacji. Przykłady Collector:

  • sumowanie np Collectors.reducing(0, (x, y) -> x + y)
  • Dołączanie StringBuilder, np Collector.of(StringBuilder::new, StringBuilder::append, StringBuilder::append, StringBuilder::toString)
Louis Wasserman
źródło
31
Spliterator (s) umożliwia również strumieniowanie Iterable, który nie jest kolekcją
Bohemian
2
Miałem na myśli „operację redukcji, w tym sensie, że termin ten ma na myśli mapowanie / redukcja”
Louis Wasserman
1
Czy Collectors.ofstara metoda wersji beta została usunięta, czy czegoś mi brakuje? Dla kompletności (x,y) -> x+ymożna zapisać jako Integer::sum.
Jean-François Savard
3
Eee, nie, przepraszam, to Collector.of, a nie Collectors.of.
Louis Wasserman,
2
Twój przykład Kolekcjonera byłby bardziej przydatny, gdybyś wyjaśnił, czym zajmuje się każdy z Twoich Kolekcjonerów.
MiguelMunoz
90

Spliterator w zasadzie oznacza „podzielny Iterator”.

Pojedynczy wątek może przechodzić / przetwarzać cały Spliterator, ale Spliterator ma również metodę, trySplit()która „oddziela” sekcję dla kogoś innego (zwykle innego wątku) do przetworzenia - pozostawiając bieżący rozdzielacz z mniejszą ilością pracy.

Collectorłączy specyfikację reducefunkcji (mapy-redukuj sławy) z wartością początkową i funkcją łączącą dwa wyniki (umożliwiając w ten sposób łączenie wyników z podzielonych strumieni pracy).

Na przykład najbardziej podstawowy Collector miałby początkową wartość 0, dodawałby liczbę całkowitą do istniejącego wyniku i „łączyłby” dwa wyniki, dodając je. W ten sposób sumując podzielony strumień liczb całkowitych.

Widzieć:

Thomas W.
źródło
wartość łącząca dwa wyniki?
Jason Law
@JasonLaw - wyjaśniono! Dzieki za sugestie.
Thomas W
5

Poniżej przedstawiono przykłady użycia predefiniowanych kolektorów do wykonywania typowych zadań redukcji mutowalności:

 // Accumulate names into a List
 List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());

 // Accumulate names into a TreeSet
 Set<String> set = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new));

 // Convert elements to strings and concatenate them, separated by commas
 String joined = things.stream()
                       .map(Object::toString)
                       .collect(Collectors.joining(", "));

 // Compute sum of salaries of employee
 int total = employees.stream()
                      .collect(Collectors.summingInt(Employee::getSalary)));

 // Group employees by department
 Map<Department, List<Employee>> byDept
     = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment));

 // Compute sum of salaries by department
 Map<Department, Integer> totalByDept
     = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment,
                                               Collectors.summingInt(Employee::getSalary)));

 // Partition students into passing and failing
 Map<Boolean, List<Student>> passingFailing =
     students.stream()
             .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
Ajay Kumar
źródło
2
To nie odpowiada na pytanie Operatora, a ponadto nie ma wyjaśnienia ani opisu twojego postu.
Sid
4

Interfejs Spliterator- to podstawowa funkcja strumieni .

W interfejsie są prezentowane metody stream()i parallelStream()metody domyślne Collection. Te metody używają Spliteratora poprzez wywołanie metody spliterator():

...

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

default Stream<E> parallelStream() {
    return StreamSupport.stream(spliterator(), true);
}

...

Spliterator to wewnętrzny iterator, który dzieli strumień na mniejsze części. Te mniejsze części mogą być przetwarzane równolegle.

Wśród innych metod są dwie najważniejsze metody zrozumienia Spliteratora:

  • boolean tryAdvance(Consumer<? super T> action) W przeciwieństwie do Iterator, próbuje wykonać operację z następnym elementem. Jeśli operacja zostanie wykonana pomyślnie, metoda zwraca true. W przeciwnym razie zwraca false- to znaczy, że nie ma elementu lub końca strumienia.

  • Spliterator<T> trySplit() Ta metoda umożliwia podzielenie zestawu danych na wiele mniejszych zestawów według jednego lub drugiego kryterium (rozmiar pliku, liczba wierszy itp.).


źródło
„Jeśli operacja została wykonana pomyślnie…” Prawdopodobnie powinieneś to przeformułować. tryAdvance javadoc jest bardziej przejrzysty: „Jeśli pozostały element istnieje, wykonuje na nim daną akcję, zwracając wartość true; else zwraca false. ”
Piro mówi, że Przywróć Monikę