Załóżmy, że mam metodę, która zwraca widok tylko do odczytu do listy członków:
class Team {
private List < Player > players = new ArrayList < > ();
// ...
public List < Player > getPlayers() {
return Collections.unmodifiableList(players);
}
}
Ponadto przypuśćmy, że wszystko, co robi klient, to powtórzenie listy raz, natychmiast. Może po to, żeby umieścić graczy w JList czy coś. Klient nie przechowuje odniesienia do listy do późniejszego wglądu!
Biorąc pod uwagę ten typowy scenariusz, czy zamiast tego powinienem zwrócić strumień?
public Stream < Player > getPlayers() {
return players.stream();
}
A może zwracanie strumienia nie jest idiomatyczne w Javie? Czy strumienie były zaprojektowane tak, aby zawsze były „kończone” wewnątrz tego samego wyrażenia, w którym zostały utworzone?
java
collections
java-8
encapsulation
java-stream
fredoverflow
źródło
źródło
players.stream()
to taka metoda, która zwraca strumień do wywołującego. Prawdziwe pytanie brzmi: czy naprawdę chcesz ograniczyć wywołującego do pojedynczego przejścia, a także odmówić mu dostępu do Twojej kolekcji przezCollection
API? Może dzwoniący po prostu chceaddAll
to do innej kolekcji?Odpowiedzi:
Odpowiedź brzmi, jak zawsze, „to zależy”. Zależy to od tego, jak duża będzie zwracana kolekcja. Zależy to od tego, czy wynik zmienia się w czasie i jak ważna jest spójność zwracanego wyniku. Zależy to w dużej mierze od tego, jak użytkownik prawdopodobnie użyje odpowiedzi.
Po pierwsze, pamiętaj, że zawsze możesz pobrać kolekcję ze strumienia i odwrotnie:
Pytanie brzmi, co jest bardziej przydatne dla dzwoniących.
Jeśli Twój wynik może być nieskończony, jest tylko jeden wybór: Stream.
Jeśli twój wynik może być bardzo duży, prawdopodobnie wolisz Stream, ponieważ może nie mieć żadnej wartości w materializacji tego wszystkiego naraz, a zrobienie tego może spowodować znaczną presję na stertę.
Jeśli wszystko, co ma zamiar wywołać, to iteracja (wyszukiwanie, filtrowanie, agregowanie), powinieneś preferować Stream, ponieważ Stream ma już te wbudowane i nie ma potrzeby materializacji kolekcji (zwłaszcza jeśli użytkownik może nie przetwarzać cały wynik). Jest to bardzo częsty przypadek.
Nawet jeśli wiesz, że użytkownik dokona iteracji wiele razy lub w inny sposób zachowa go w pobliżu, nadal możesz chcieć zamiast tego zwrócić strumień, z prostego powodu, że niezależnie od kolekcji, w której zdecydujesz się go umieścić (np. ArrayList), może nie być formularz, który chcą, a następnie dzwoniący i tak musi go skopiować. jeśli zwrócisz strumień, mogą to zrobić
collect(toCollection(factory))
i uzyskać dokładnie taką formę, jaką chcą.Powyższe przypadki „preferuj Stream” wynikają głównie z faktu, że Stream jest bardziej elastyczny; możesz później powiązać się z tym, jak go używasz, bez ponoszenia kosztów i ograniczeń związanych z materializacją go w kolekcji.
Jedynym przypadkiem, w którym musisz zwrócić kolekcję, jest sytuacja, gdy istnieją wysokie wymagania dotyczące spójności i musisz stworzyć spójną migawkę poruszającego się celu. Następnie będziesz chciał umieścić elementy w kolekcji, która się nie zmieni.
Powiedziałbym więc, że w większości przypadków Stream jest właściwą odpowiedzią - jest bardziej elastyczny, nie narzuca zwykle niepotrzebnych kosztów materializacji iw razie potrzeby można go łatwo przekształcić w wybraną przez Ciebie Kolekcję. Ale czasami może być konieczne zwrócenie kolekcji (powiedzmy, ze względu na rygorystyczne wymagania dotyczące spójności) lub możesz chcieć zwrócić kolekcję, ponieważ wiesz, w jaki sposób użytkownik będzie z niej korzystać, i wiesz, że jest to dla niego najwygodniejsze.
źródło
Chciałbym dodać kilka uwag do doskonałej odpowiedzi Briana Goetza .
Dość często zwraca się Stream z wywołania metody w stylu „getter”. Zobacz stronę Wykorzystanie strumienia w Java 8 javadoc i poszukaj „metod ..., które zwracają Stream” dla pakietów innych niż
java.util.Stream
. Te metody są zwykle na klasach, które reprezentują lub mogą zawierać wiele wartości lub agregacji czegoś. W takich przypadkach interfejsy API zwykle zwracają ich kolekcje lub tablice. Ze wszystkich powodów, które Brian zauważył w swojej odpowiedzi, dodawanie tutaj metod zwracających strumień jest bardzo elastyczne. Wiele z tych klas ma już metody zwracające kolekcje lub tablice, ponieważ są one wcześniejsze niż interfejs API strumieni. Jeśli projektujesz nowy interfejs API i sensowne jest zapewnienie metod zwracających strumień, może nie być również konieczne dodawanie metod zwracających kolekcje.Brian wspomniał o koszcie „materializacji” wartości w kolekcji. Aby wzmocnić ten punkt, istnieją tutaj dwa koszty: koszt przechowywania wartości w kolekcji (alokacja pamięci i kopiowanie), a także koszt tworzenia wartości w pierwszej kolejności. Ten ostatni koszt można często zmniejszyć lub uniknąć, wykorzystując lenistwo poszukiwania przez Strumień. Dobrym tego przykładem są interfejsy API w
java.nio.file.Files
:Nie tylko
readAllLines
musi przechowywać całą zawartość pliku w pamięci, aby zapisać go na liście wyników, ale także musi odczytać plik do samego końca, zanim zwróci listę.lines
Metoda może powrócić niemal natychmiast po tym, jak wykonać pewne konfiguracji, pozostawiając czytanie pliku i linia łamania dopiero później, gdy jest to konieczne - albo wcale. To ogromna korzyść, jeśli np. Dzwoniącego interesują tylko pierwsze dziesięć linii:Oczywiście można zaoszczędzić sporo miejsca w pamięci, jeśli wywołujący przefiltruje strumień, aby zwrócić tylko wiersze pasujące do wzorca itp.
Idiom, który wydaje się pojawiać, polega na nazywaniu metod zwracających strumień po liczbie mnogiej nazwy rzeczy, które reprezentuje lub zawiera, bez
get
przedrostka. Ponadto, chociażstream()
jest to rozsądna nazwa metody zwracającej strumień, gdy istnieje tylko jeden możliwy zestaw wartości do zwrócenia, czasami istnieją klasy, które mają agregacje wielu typów wartości. Na przykład załóżmy, że masz jakiś obiekt, który zawiera zarówno atrybuty, jak i elementy. Możesz dostarczyć dwa interfejsy API zwracające strumień:źródło
Tak są używane w większości przykładów.
Uwaga: zwrócenie strumienia nie różni się zbytnio od zwrotu Iteratora (przyznane z dużo większą mocą ekspresji)
IMHO najlepszym rozwiązaniem jest podsumowanie, dlaczego to robisz, i nie zwracanie kolekcji.
na przykład
lub jeśli zamierzasz je policzyć
źródło
Jeśli strumień jest skończony, a na zwracanych obiektach jest oczekiwana / normalna operacja, która wyrzuci sprawdzony wyjątek, zawsze zwracam Collection. Ponieważ jeśli zamierzasz robić coś na każdym z obiektów, które mogą rzucić wyjątek check, znienawidzisz strumień. Jeden prawdziwy brak w przypadku strumieni to brak możliwości eleganckiego radzenia sobie ze sprawdzonymi wyjątkami.
Może to znak, że nie potrzebujesz sprawdzonych wyjątków, co jest sprawiedliwe, ale czasami są nieuniknione.
źródło
W przeciwieństwie do kolekcji strumienie mają dodatkowe cechy . Strumień zwracany dowolną metodą może wyglądać następująco:
Te różnice istnieją również w kolekcjach, ale są częścią oczywistej umowy:
Jako konsument strumienia (ze zwrotu metody lub jako parametr metody) jest to niebezpieczna i zagmatwana sytuacja. Aby upewnić się, że ich algorytm działa poprawnie, odbiorcy strumieni muszą upewnić się, że algorytm nie przyjmuje błędnych założeń dotyczących charakterystyki strumienia. A to jest bardzo trudne do zrobienia. W testach jednostkowych oznaczałoby to, że musisz pomnożyć wszystkie testy, aby zostały powtórzone z tą samą zawartością strumienia, ale ze strumieniami, które są
Pisanie zabezpieczeń metod dla strumieni, które generują wyjątek IllegalArgumentException, jeśli strumień wejściowy ma cechy, które naruszają algorytm, jest trudne, ponieważ właściwości są ukryte.
To pozostawia Stream tylko jako ważny wybór w sygnaturze metody, gdy żaden z powyższych problemów nie ma znaczenia, co rzadko się zdarza.
Znacznie bezpieczniej jest używać innych typów danych w sygnaturach metod z jawną umową (i bez niejawnego przetwarzania puli wątków), która uniemożliwia przypadkowe przetwarzanie danych z błędnymi założeniami dotyczącymi uporządkowania, wielkości lub równoległości (i użycia puli wątków).
źródło
Myślę, że to zależy od twojego scenariusza. Być może, jeśli zrobisz swoje
Team
narzędzieIterable<Player>
, wystarczy.lub w stylu funkcjonalnym:
Ale jeśli chcesz mieć bardziej kompletny i płynny interfejs API, dobrym rozwiązaniem może być strumień.
źródło
stream.count()
jest też całkiem łatwy), liczba ta nie ma większego znaczenia dla niczego innego niż debugowanie lub szacowanie.Chociaż niektórzy bardziej znani respondenci udzielili świetnych ogólnych porad, jestem zaskoczony, że nikt nie powiedział:
Jeśli masz już „zmaterializowane”
Collection
w ręku (tj. Zostało już utworzone przed wywołaniem - jak to ma miejsce w podanym przykładzie, gdzie jest to pole składowe), nie ma sensu przekształcać go wStream
. Dzwoniący może z łatwością to zrobić samodzielnie. Natomiast jeśli wywołujący chce korzystać z danych w ich oryginalnej postaci, konwertowanie ich do formatu aStream
zmusza ich do wykonania zbędnej pracy w celu ponownego zmaterializowania kopii oryginalnej struktury.źródło
źródło
Prawdopodobnie miałbym 2 metody, jedną do zwrócenia a,
Collection
a drugą do zwrócenia kolekcji jakoStream
.To najlepsze z obu światów. Klient może wybrać, czy chce Listę, czy Strumień, i nie musi tworzyć dodatkowego obiektu polegającego na tworzeniu niezmiennej kopii listy tylko po to, aby uzyskać strumień.
To również dodaje tylko 1 dodatkową metodę do twojego API, więc nie masz zbyt wielu metod
źródło