Wygląda na to, że mam problem ze zrozumieniem, w jaki sposób Java komponuje operacje strumieniowe do potoku strumieniowego.
Podczas wykonywania następującego kodu
public
static void main(String[] args) {
StringBuilder sb = new StringBuilder();
var count = Stream.of(new String[]{"1", "2", "3", "4"})
.map(sb::append)
.count();
System.out.println(count);
System.out.println(sb.toString());
}
Konsola drukuje tylko 4
. StringBuilder
Obiekt nadal ma wartość ""
.
Po dodaniu operacji filtrowania: filter(s -> true)
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
var count = Stream.of(new String[]{"1", "2", "3", "4"})
.filter(s -> true)
.map(sb::append)
.count();
System.out.println(count);
System.out.println(sb.toString());
}
Wyjście zmienia się na:
4
1234
W jaki sposób ta pozornie zbędna operacja filtrowania zmienia zachowanie złożonego potoku strumienia?
java
java-stream
atalantus
źródło
źródło
Odpowiedzi:
Operacja
count()
terminalu w mojej wersji JDK kończy się na wykonaniu następującego kodu:Jeśli
filter()
w potoku operacji znajduje się operacja, wielkość strumienia, który jest znany na początku, nie może być już znana (ponieważfilter
może odrzucić niektóre elementy strumienia). Tak więcif
blok nie jest wykonywany, operacje pośrednie są wykonywane, a StringBuilder jest modyfikowany.Z drugiej strony, jeśli masz tylko
map()
w potoku, liczba elementów w strumieniu jest na pewno taka sama jak początkowa liczba elementów. Tak więc blok if jest wykonywany, a rozmiar jest zwracany bezpośrednio, bez oceny operacji pośrednich.Zwróć uwagę, że przekazana lambda
map()
narusza umowę zdefiniowaną w dokumentacji: ma to być bezpaństwowa operacja bezstanowa, ale nie jest bezpaństwowa. Tak więc posiadanie innego wyniku w obu przypadkach nie może być uważane za błąd.źródło
flatMap()
może być w stanie zmienić liczbę elementów, czy to był powód, dla którego początkowo był chętny (teraz leniwy)? Tak więc alternatywą byłoby użycieforEach()
i liczenie osobno, jeślimap()
w obecnej formie narusza umowę, tak sądzę.4 1234
bez korzystania z dodatkowego filtra lub wywoływania efektów ubocznych w operacji map ()?int count = array.length; String result = String.join("", array);
Collectors.joining("")
W jdk-9 było to wyraźnie udokumentowane w dokumentach Java
Uwaga API:
źródło
Nie do tego służy .map. Ma to służyć do przekształcania strumienia „Coś” w strumień „Coś innego”. W tym przypadku używasz mapy, aby dołączyć ciąg do zewnętrznego Stringbuilder, po czym masz strumień „Stringbuilder”, z których każdy został utworzony przez operację mapowania, dodając jeden numer do oryginalnego Stringbuilder.
Strumień faktycznie nie robi nic z odwzorowanymi wynikami w strumieniu, więc całkowicie uzasadnione jest założenie, że procesor może pominąć ten krok. Liczysz się na efekty uboczne, które psują funkcjonalny model mapy. Lepiej skorzystasz z usługi forEach. Wykonaj liczenie jako osobny strumień w całości lub umieść licznik za pomocą AtomicInt w forEach.
Filtr zmusza go do uruchomienia zawartości strumienia, ponieważ musi on teraz zrobić coś sensownie znaczącego z każdym elementem strumienia.
źródło