Jakie są zalety strumieni w stosunku do pętli w Javie? [Zamknięte]

137

Zapytano mnie o to podczas wywiadu i nie jestem przekonany, że udzieliłem najlepszej odpowiedzi, jaką mogłem mieć. Wspomniałem, że możesz przeprowadzić wyszukiwanie równoległe i że wartości zerowe były obsługiwane w jakiś sposób, którego nie pamiętałem. Teraz zdaję sobie sprawę, że myślałem o Optionals. Czego tu brakuje? Twierdzą, że to lepszy lub bardziej zwięzły kod, ale nie jestem pewien, czy się zgadzam.


Biorąc pod uwagę, jak zwięźle udzielono na to odpowiedzi, wydaje się, że nie było to jednak zbyt szerokie pytanie.


Jeśli zadają to pytanie podczas wywiadów i oczywiście tak jest, czemu może służyć jego rozbicie, a nie utrudnianie znalezienia odpowiedzi? To znaczy, czego szukasz? Mogłabym rozbić pytanie i uzyskać odpowiedzi na wszystkie pytania podrzędne, ale potem utworzyć pytanie nadrzędne z linkami do wszystkich pytań podrzędnych ... wydaje się jednak dość głupie. Skoro już o tym mowa, proszę podać przykład mniej ogólnego pytania. Nie wiem, jak zadać tylko część tego pytania i nadal uzyskać sensowną odpowiedź. Mógłbym zadać dokładnie to samo pytanie w inny sposób. Na przykład mógłbym zapytać „Czemu służą strumienie?” lub „Kiedy powinienem używać strumienia zamiast pętli for?” lub „Po co przejmować się strumieniami zamiast pętli for?” Są to jednak dokładnie to samo pytanie.

... czy też jest uważany za zbyt szeroki, ponieważ ktoś udzielił naprawdę długiej, wielopunktowej odpowiedzi? Szczerze mówiąc, każdy wtajemniczony mógłby to zrobić z praktycznie każdym pytaniem. Jeśli na przykład jesteś jednym z autorów JVM, prawdopodobnie mógłbyś mówić o pętlach for przez cały dzień, podczas gdy większość z nas nie mogła.

„Zmień pytanie, aby ograniczyć je do konkretnego problemu, z dostateczną szczegółowością, aby znaleźć odpowiednią odpowiedź. Unikaj zadawania wielu odrębnych pytań naraz. Zobacz stronę Jak zadawać, aby uzyskać pomoc w wyjaśnieniu tego pytania”.

Jak zauważono poniżej, udzielono adekwatnej odpowiedzi, która dowodzi, że istnieje i jest wystarczająco łatwa do udzielenia.

user447607
źródło
9
To jest oparte na opiniach imho. Osobiście wolę strumienie, ponieważ dzięki temu kod jest bardziej czytelny. Pozwala pisać, co chcesz, a nie jak . Co więcej, robienie niesamowitych rzeczy z jednowierszowymi jest totalnie złe.
Arnaud Denoyelle
19
Nawet jeśli to 30 linii, jedna wkładka? Nie przepadam za długimi łańcuchami.
user447607
1
Poza tym wszystko, czego tutaj szukam, to odpowiednia odpowiedź na rozmowę kwalifikacyjną. To jedyna „opinia”, która ma znaczenie.
user447607
1
Mówiąc edukacyjnie, to pytanie uratowało mi również pewne poniżenie podczas przyszłej rozmowy kwalifikacyjnej, @slim naprawdę go przybiło, ale mówiąc przemysłowo, mówi również o tym, jak języki programowania Microsoft zbudowały swoje kariery na wyrywaniu języka java, a ostatecznie java mści się, zgrywając off the Lambda Expression i strumieni od przeciwników, zobaczmy, co java zrobi z Structs i Unions w przyszłości :)
ShayHaned
5
Zwróć uwagę, że strumienie wykorzystują tylko ułamek mocy w programowaniu funkcjonalnym: - /
Thorbjørn Ravn Andersen

Odpowiedzi:

264

Ciekawe, że pytanie w wywiadzie dotyczy zalet, bez pytania o wady, bo są i jedno.

Strumienie mają bardziej deklaratywny styl . Lub bardziej wyrazisty styl. Można uznać, że lepiej jest zadeklarować swój zamiar w kodzie, niż opisać, jak to się robi:

 return people
     .filter( p -> p.age() < 19)
     .collect(toList());

... mówi dość wyraźnie, że filtrujesz pasujące elementy z listy, podczas gdy:

 List<Person> filtered = new ArrayList<>();
 for(Person p : people) {
     if(p.age() < 19) {
         filtered.add(p);
     }
 }
 return filtered;

Mówi „Robię pętlę”. Cel pętli tkwi głębiej w logice.

Strumienie są często bardziej zwięzłe . Ten sam przykład to pokazuje. Terser nie zawsze jest lepszy, ale jeśli potrafisz być jednocześnie zwięzły i ekspresyjny, tym lepiej.

Strumienie mają silne podobieństwo do funkcji . Java 8 wprowadza lambdy i interfejsy funkcjonalne, co otwiera cały zestaw zaawansowanych technik. Strumienie zapewniają najwygodniejszy i najbardziej naturalny sposób stosowania funkcji do sekwencji obiektów.

Strumienie zachęcają do mniejszej zmienności . Jest to w pewnym sensie związane z aspektem programowania funkcjonalnego - programy, które piszesz przy użyciu strumieni, są typem programów, w których nie modyfikujesz obiektów.

Strumienie zachęcają do luźniejszych sprzężeń . Twój kod obsługi strumienia nie musi znać źródła strumienia ani jego ostatecznej metody kończenia.

Strumienie mogą w zwięzły sposób wyrażać dość wyrafinowane zachowanie . Na przykład:

 stream.filter(myfilter).findFirst();

Na pierwszy rzut oka może wyglądać tak, jakby filtrował cały strumień, a następnie zwraca pierwszy element. Ale tak naprawdę findFirst()napędza całą operację, więc skutecznie zatrzymuje się po znalezieniu jednej pozycji.

Strumienie umożliwiają przyszły wzrost wydajności . Niektórzy przeprowadzili testy porównawcze i stwierdzili, że strumienie jednowątkowe z pamięci Listlub tablic w pamięci mogą być wolniejsze niż równoważna pętla. Jest to prawdopodobne, ponieważ w grze jest więcej obiektów i kosztów ogólnych.

Ale strumienie się skalują. Oprócz wbudowanej obsługi Java dla operacji równoległych strumieni, istnieje kilka bibliotek do rozproszonej redukcji map przy użyciu strumieni jako API, ponieważ model pasuje.

Niedogodności?

Wydajność : forpętla przez tablicę jest niezwykle lekka zarówno pod względem wykorzystania sterty, jak i procesora. Jeśli priorytetem jest surowa prędkość i oszczędność pamięci, używanie strumienia jest gorsze.

Znajomość . Świat jest pełen doświadczonych programistów proceduralnych z wielu środowisk językowych, którym pętle są znane, a strumienie są nowatorskie. W niektórych środowiskach chcesz napisać kod, który jest znany takiej osobie.

Narzut poznawczy . Ze względu na deklaratywną naturę i zwiększoną abstrakcję od tego, co dzieje się pod spodem, może być konieczne zbudowanie nowego modelu mentalnego określającego, jak kod odnosi się do wykonywania. W rzeczywistości musisz to zrobić tylko wtedy, gdy coś pójdzie nie tak lub jeśli musisz dogłębnie przeanalizować wydajność lub subtelne błędy. Kiedy to „po prostu działa”, po prostu działa.

Debugery są ulepszane, ale nawet teraz, gdy przechodzisz przez kod strumieniowy w debugerze, może to być trudniejsze do wykonania niż równoważna pętla, ponieważ prosta pętla jest bardzo blisko zmiennych i lokalizacji kodu, z którymi współpracuje tradycyjny debugger.

szczupły
źródło
4
Myślę, że byłoby uczciwe, gdybyśmy to schludni, że rzeczy podobne do streamów stają się coraz bardziej powszechne i teraz pojawiają się w wielu powszechnie używanych językach, które nie są specjalnie zorientowane na FP.
Casey
6
Biorąc pod uwagę wymienione tutaj zalety i wady, uważam, że strumienie NIE są tego warte do niczego innego niż bardzo proste zastosowania (mała logika jeśli / wtedy / inaczej, niewiele zagnieżdżonych wywołań lub lambd itp.), W niekrytycznych częściach wydajności
Henrik Kjus Alstad
2
@HenrikKjusAlstad To absolutnie nie jest rzecz, którą chciałem przekazać. Strumienie są dojrzałe, wydajne, wyraziste i całkowicie odpowiednie dla kodu produkcyjnego.
szczupły
Och, nie miałem na myśli, że nie użyję go w produkcji. Zamiast tego domyślnie stosowałbym staromodne pętle / ifs itp. Zamiast strumieni, zwłaszcza jeśli wynikowy strumień wyglądałby na złożony. Jestem pewien, że istnieją zastosowania, w których strumień pokonuje pętle i jeśli jest wyraźny, ale częściej niż nie, myślę, że jest to „powiązane”, a czasem nawet na odwrót. Dlatego położyłem nacisk na poznawczy argument narzutu za trzymaniem się starych sposobów.
Henrik Kjus Alstad
1
@lijepdam - ale nadal masz kod, który mówi „Iteruję po tej liście (zobacz wewnątrz pętli, aby dowiedzieć się, dlaczego)”, podczas gdy „iterowanie po liście” nie jest głównym celem kodu.
slim
16

Pomijając zabawę składniową, strumienie są zaprojektowane do pracy z potencjalnie nieskończenie dużymi zbiorami danych, podczas gdy tablice, kolekcje i prawie każda klasa Java SE, która implementuje Iterowalność, są całkowicie w pamięci.

Wadą strumienia jest to, że filtry, mapowania itp. Nie mogą zgłaszać sprawdzonych wyjątków. To sprawia, że ​​Stream jest złym wyborem dla, powiedzmy, pośrednich operacji we / wy.

VGR
źródło
8
Oczywiście możesz też pętlować nieskończone źródła.
slim
Ale jeśli elementy do przetworzenia są utrwalane w bazie danych, w jaki sposób używasz strumieni? Młodszego programisty może pokusić się o przeczytanie ich wszystkich w Kolekcji tylko po to, aby użyć strumieni. A to byłaby katastrofa.
Lluis Martinez
2
@LluisMartinez dobra biblioteka kliencka DB zwróci coś podobnego Stream<Row>- lub byłoby możliwe napisanie własnej Streamimplementacji opakowującej operacje kursora wynikowego DB .
slim
To, że strumienie po cichu ignorują wyjątki, to błąd, który niedawno mnie ugryzł. Nieintuicyjne.
xxfelixxx
Strumienie @xxfelixxx nie ignorują po cichu wyjątków. Spróbuj uruchomić to:Arrays.asList("test", null).stream().forEach(s -> System.out.println(s.length()));
VGR,
8
  1. Zrozumiałeś nieprawidłowo: operacje równoległe używają Streams, Optionala nie s.

  2. Możesz zdefiniować metody pracujące ze strumieniami: przyjmować je jako parametry, zwracać itp. Nie możesz zdefiniować metody, która przyjmuje pętlę jako parametr. Pozwala to na jednorazową skomplikowaną operację strumienia i wielokrotne korzystanie z niego. Zwróć uwagę, że Java ma tutaj wadę: twoje metody muszą być wywoływane w someMethod(stream)przeciwieństwie do własnych strumieni stream.someMethod(), więc mieszanie ich komplikuje czytanie: spróbuj zobaczyć kolejność operacji w

    myMethod2(myMethod(stream.transform(...)).filter(...))
    

    Wiele innych języków (C #, Kotlin, Scala itp.) Dopuszcza pewną formę „metod rozszerzających”.

  3. Nawet jeśli potrzebujesz tylko operacji sekwencyjnych i nie chcesz ich ponownie używać, aby móc używać strumieni lub pętli, proste operacje na strumieniach mogą odpowiadać dość złożonym zmianom w pętlach.

Alexey Romanov
źródło
Wyjaśnij 1. Czy opcjonalny interfejs nie jest środkiem, za pomocą którego wartości null są obsługiwane w łańcuchach? Jeśli chodzi o 3, ma to sens, ponieważ przy zwartych filtrach metoda będzie wywoływana tylko dla określonych wystąpień. Wydajny. To ma sens, że mogę stwierdzić, że ich użycie zmniejsza potrzebę pisania dodatkowego kodu, który będzie musiał zostać przetestowany itp. Po przejrzeniu, nie jestem pewien, co masz na myśli przez sekwencyjny przypadek w 2.
user447607
1. Optionaljest alternatywą dla null, ale nie ma nic wspólnego z operacjami równoległymi. Chyba że „Teraz zdaję sobie sprawę, że myślałem o opcjach” w twoim pytaniu dotyczy tylko nullobsługi?
Alexey Romanov
Zmieniłem kolejność 2 i 3 i trochę rozszerzyłem oba.
Alexey Romanov
7

Pętla się nad sekwencją (tablica, kolekcja, dane wejściowe, ...), ponieważ chcesz zastosować jakąś funkcję do elementów sekwencji.

Strumienie dają możliwość komponowania funkcji na elementach sekwencji i pozwalają na implementację większości typowych funkcji (np. Mapowania, filtrowania, wyszukiwania, sortowania, zbierania, ...) niezależnie od konkretnego przypadku.

Dlatego biorąc pod uwagę pewne zadanie zapętlenia, w większości przypadków można to wyrazić za pomocą mniejszego kodu za pomocą strumieni, tj. Zyskujesz czytelność .

wero
źródło
5
Cóż, to nie tylko czytelność. Kod, którego nie musisz pisać, to kod, którego nie musisz testować.
user447607
3
Wygląda na to, że masz dobre odpowiedzi na swój wywiad
wero
6

Powiedziałbym, że jego równoległość jest tak łatwa w użyciu. Spróbuj iterować ponad miliony wpisów równolegle z pętlą for. Idziemy do wielu procesorów, nie szybciej; więc im łatwiej jest biegać równolegle, tym lepiej, a przy Streams to pestka.

Bardzo podoba mi się ich gadatliwość . Zrozumienie tego, co faktycznie robią i produkują, zajmuje niewiele czasu, a nie tego, jak to robią.

Eugene
źródło