Mogę dodać strumienie lub dodatkowe elementy, na przykład:
Stream stream = Stream.concat(stream1, Stream.concat(stream2, Stream.of(element));
I mogę dodawać nowe rzeczy na bieżąco, na przykład:
Stream stream = Stream.concat(
Stream.concat(
stream1.filter(x -> x!=0), stream2)
.filter(x -> x!=1),
Stream.of(element))
.filter(x -> x!=2);
Ale to jest brzydkie, ponieważ concat
jest statyczne. Gdyby concat
była to metoda instancji, powyższe przykłady byłyby znacznie łatwiejsze do odczytania:
Stream stream = stream1.concat(stream2).concat(element);
I
Stream stream = stream1
.filter(x -> x!=0)
.concat(stream2)
.filter(x -> x!=1)
.concat(element)
.filter(x -> x!=2);
Moje pytanie brzmi:
1) Czy jest jakiś dobry powód, dla którego concat
jest statyczny? A może brakuje mi jakiejś równoważnej metody instancji?
2) W każdym razie czy istnieje lepszy sposób na zrobienie tego?
java
concat
java-8
java-stream
MarcG
źródło
źródło
Odpowiedzi:
Jeśli dodać importu statycznych dla Stream.concat i Stream.of , pierwszy przykład można zapisać w następujący sposób:
Importowanie metod statycznych z nazwami rodzajowymi może spowodować, że kod stanie się trudny do odczytania i utrzymania ( zanieczyszczenie przestrzeni nazw ). Dlatego lepiej byłoby utworzyć własne metody statyczne o bardziej znaczących nazwach. Jednak dla demonstracji pozostanę przy tej nazwie.
W przypadku tych dwóch metod statycznych (opcjonalnie w połączeniu ze statycznymi importami) oba przykłady można zapisać w następujący sposób:
Kod jest teraz znacznie krótszy. Zgadzam się jednak, że czytelność się nie poprawiła. Więc mam inne rozwiązanie.
W wielu sytuacjach Collectors można wykorzystać do rozszerzenia funkcjonalności strumieni. Mając dwa Kolektory na dole, dwa przykłady można zapisać w następujący sposób:
Jedyna różnica między żądaną składni i składni powyższego jest to, że trzeba wymienić concat (...) z honor (concat (...)) . Dwie metody statyczne można zaimplementować w następujący sposób (opcjonalnie używane w połączeniu z importami statycznymi):
Oczywiście rozwiązanie to ma wadę, o której należy wspomnieć. zbieranie to operacja końcowa, która zużywa wszystkie elementy strumienia. Ponadto concat kolektorów tworzy pośrednią ArrayList za każdym razem, gdy jest ona używana w łańcuchu. Obie operacje mogą mieć znaczący wpływ na zachowanie programu. Jeśli jednak czytelność jest ważniejsza niż wydajność , może to być bardzo pomocne podejście.
źródło
concat
kolekcjoner nie jest zbyt czytelny. Wydaje się dziwne, że jednoparametrowa metoda statyczna ma taką nazwę, a także ma być używanacollect
do konkatenacji.It's a bad idea to import static methods with names
? Jestem szczerze zainteresowany - uważam, że dzięki temu kod jest bardziej zwięzły i czytelny, a wiele osób, o które pytałem, myślało tak samo. Spróbuj podać kilka przykładów, dlaczego jest to ogólnie złe?compare(reverse(getType(42)), of(6 * 9).hashCode())
? Zauważ, że nie powiedziałem, że statyczne importowanie to zły pomysł, ale statyczne importowanie dla nazw rodzajowych, takich jakof
iconcat
są.Niestety ta odpowiedź jest prawdopodobnie mało pomocna lub żadna, ale przeprowadziłem analizę kryminalistyczną listy mailingowej Java Lambda, aby sprawdzić, czy mogę znaleźć przyczynę tego projektu. Oto, czego się dowiedziałem.
Na początku była metoda instancji dla Stream.concat (Stream)
Na liście mailingowej wyraźnie widzę, że metoda została pierwotnie zaimplementowana jako metoda instancji, o czym możesz przeczytać w tym wątku Paul Sandoz o operacji konkatacji.
Omawiają w nim kwestie, które mogą wyniknąć z tych przypadków, w których strumień mógłby być nieskończony i co oznaczałoby w tych przypadkach konkatenację, ale nie sądzę, aby to było przyczyną modyfikacji.
W tym innym wątku widać, że niektórzy wczesni użytkownicy JDK 8 kwestionowali zachowanie metody instancji concat, gdy jest używana z argumentami zerowymi.
Ten drugi wątek ujawnia jednak, że projekt metody concat był przedmiotem dyskusji.
Refaktoryzacja do Streams.concat (Stream, Stream)
Ale bez żadnego wyjaśnienia nagle metody zostały zmienione na metody statyczne, jak widać w tym wątku o łączeniu strumieni . Jest to prawdopodobnie jedyny wątek wiadomości, który rzuca trochę światła na tę zmianę, ale nie było dla mnie wystarczająco jasne, aby określić przyczynę refaktoryzacji. Ale widzimy, że wykonali zatwierdzenie, w którym zasugerowali przeniesienie
concat
metody zStream
klasy pomocniczej do klasy pomocniczejStreams
.Refaktoryzacja do Stream.concat (Stream, Stream)
Później został ponownie przeniesiony z
Streams
doStream
, ale po raz kolejny bez wyjaśnienia.Podsumowując, powód powstania projektu nie jest dla mnie do końca jasny i nie mogłem znaleźć dobrego wyjaśnienia. Myślę, że nadal możesz zadać pytanie na liście mailingowej.
Niektóre alternatywy dla konkatenacji strumieni
Ten drugi wątek autorstwa Michaela Hixsona omawia / pyta o inne sposoby łączenia / konkatowania strumieni
źródło
public static <T> Stream<T> concat(Stream<T>... streams) { return Stream.of(streams).reduce(Stream.empty(), Stream::concat);}
@SafeVarargs private static <T> Stream<T> concat(Stream<? extends T>... streams) { return Stream.of(streams).reduce(Stream.empty(),Stream::concat).map(Function.identity());}
Function.identity()
mapy? W końcu zwraca ten sam argument, który otrzymał. Nie powinno to mieć żadnego wpływu na wynikowy strumień. Czy coś mi brakuje?return Stream.of(streams).reduce(Stream.empty(),Stream::concat)
zwraca Stream <? extends T>. (Coś <T> jest podtypem Coś <? extends T>, a nie odwrotnie, więc nie można tego rzucić) Dodatkowe.map(identity())
rzutowanie <? rozszerza T> do <T>. Dzieje się tak dzięki połączeniu argumentów metod i typów zwracanych w języku Java 8, a także sygnatury metody map (). W rzeczywistości jest to tożsamość Function. <T> ().? extends T
, ponieważ możesz użyć konwersji przechwytywania . W każdym razie, oto mój fragment kodu streszczenia . Kontynuujmy dyskusję w streszczeniu.Moja biblioteka StreamEx rozszerza funkcjonalność Stream API. W szczególności oferuje metody takie jak dołączanie i poprzedzanie, które rozwiązują ten problem (używają wewnętrznie
concat
). Te metody mogą akceptować inny strumień, kolekcję lub tablicę varargs. Korzystając z mojej biblioteki, twój problem można rozwiązać w ten sposób (zauważ, żex != 0
wygląda to dziwnie w przypadku nieprymitywnego strumienia):Przy okazji jest też skrót do Twojej
filter
operacji:źródło
Po prostu zrób:
gdzie
identity()
jest statyczny import plikówFunction.identity()
.Łączenie wielu strumieni w jeden strumień jest tym samym, co spłaszczanie strumienia.
Jednak niestety z jakiegoś powodu nie ma
flatten()
włączonej metodyStream
, więc musisz użyćflatMap()
funkcji tożsamości.źródło
Możesz użyć metody Guava , która będzie bardzo krótka w przypadku importu statycznego:
Streams
.
concat(Stream<? extends T>... streams)
źródło
Jeśli nie masz nic przeciwko korzystaniu z bibliotek zewnętrznych, cyklop-reakcja ma rozszerzony typ strumienia, który pozwoli ci to zrobić za pomocą operatorów dołączania / uzupełniania.
Poszczególne wartości, tablice, elementy iteracyjne, strumienie lub strumienie reaktywne Wydawcy mogą być dołączani i dodawani na początku jako metody instancji.
[Ujawnienie, że jestem głównym twórcą Cyclops-React]
źródło
Ostatecznie nie interesuje mnie łączenie strumieni, ale uzyskanie połączonego wyniku przetwarzania każdego elementu wszystkich tych strumieni.
Chociaż łączenie strumieni może okazać się uciążliwe (stąd ten wątek), łączenie ich wyników przetwarzania jest dość łatwe.
Kluczem do rozwiązania jest utworzenie własnego kolektora i upewnienie się, że funkcja dostawcy dla nowego kolektora zwraca za każdym razem tę samą kolekcję ( nie nową ), poniższy kod ilustruje to podejście.
źródło
Co powiesz na napisanie własnej metody konkatacji?
To przynajmniej sprawia, że twój pierwszy przykład jest dużo bardziej czytelny.
źródło