Pobieranie listy z java.util.stream.Stream w Javie 8

443

Bawiłem się lambdami Java 8, aby łatwo filtrować kolekcje. Ale nie znalazłem zwięzłego sposobu na uzyskanie wyniku jako nowej listy w ramach tej samej instrukcji. Oto moje najbardziej zwięzłe podejście do tej pory:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = new ArrayList<>();
sourceLongList.stream().filter(l -> l > 100).forEach(targetLongList::add);

Przykłady w sieci nie odpowiedziały na moje pytanie, ponieważ zatrzymały się bez wygenerowania nowej listy wyników. Musi być bardziej zwięzły sposób. Liczyłam, że Streamklasa ma sposobów toList(), toSet()...

Czy istnieje sposób, aby zmienne targetLongListmożna przypisać bezpośrednio w trzecim wierszu?

Daniel K.
źródło
7
Jeśli nie potrzebujesz sourceLongListpóźniej, jest Collection.removeIf(…)dla wygody.
Holger
2
Co powiesz na to? List<Long> targetLongList = sourceLongList.stream().collect(Collectors.toList());
leeyuiwah

Odpowiedzi:

609

To, co robisz, może być najprostszym sposobem, pod warunkiem, że Twój strumień pozostaje sekwencyjny - w przeciwnym razie będziesz musiał wcześniej wywołać funkcję sekwencyjną () forEach.

[późniejsza edycja: powodem, dla którego konieczne jest wywołanie funkcji sekwencyjnej (), jest to, że kod w jego obecnej postaci ( forEach(targetLongList::add)) byłby bardziej ryzykowny, gdyby strumień był równoległy. Nawet wtedy nie osiągnie zamierzonego efektu, co forEachjest wyraźnie niedeterministyczne - nawet w strumieniu sekwencyjnym kolejność przetwarzania elementów nie jest gwarantowana. Musisz użyć, forEachOrderedaby zapewnić prawidłowe zamówienie. Intencją projektantów Stream API jest użycie kolektora w takiej sytuacji, jak poniżej.]

Alternatywą jest

targetLongList = sourceLongList.stream()
    .filter(l -> l > 100)
    .collect(Collectors.toList());
Maurice Naftalin
źródło
10
Dodatek: Myślę, że kody te stają się nieco krótsze, wyraźniejsze i ładniejsze, jeśli używasz importu statycznego toList. Odbywa się to poprzez umieszczenie następujących wśród importu pliku: static import java.util.stream.Collectors.toList;. Następnie połączenie odbiera tylko .collect(toList()).
Lii
4
W środowisku Eclipse możliwe jest, aby IDE dodało statyczny import metod. Odbywa się to poprzez dodanie Collectorsklasy w Preferencjach -> Java -> Edytor -> Content Assist -> Ulubione . Następnie wystarczy wpisać toLiw Ctr + Spacja, aby wypełnić IDE toListi dodać import statyczny.
Lii
3
Należy pamiętać, że IntStreamniektóre inne prawie-ale-nie-całkiem Streamnie mają tej collect(Collector)metody i będziesz musiał zadzwonić, IntStream.boxed()aby przekonwertować je na regularne Stream. Z drugiej strony, może po prostu chcesz toArray().
Mutant Bob
dlaczego musimy użyć sequential()przed forEachlub użyć „dla każdego zamówienia”
amarnath
1
@amarnathharish Ponieważ forEach nie gwarantuje kolejności wykonywania operacji dla strumienia równoległego. JavaDoc mówi: „Zachowanie tej operacji jest wyraźnie niedeterministyczne. W przypadku potoków strumienia równoległego ta operacja nie gwarantuje przestrzegania kolejności spotkań strumienia, ponieważ poświęcenie to przyniesie korzyść z równoległości”. (Pierwsze zdanie tego cytatu w rzeczywistości oznacza, że ​​kolejność strumieni nie jest gwarantowana, chociaż w praktyce jest zachowana.)
Maurice Naftalin,
183

Zaktualizowano:

Innym podejściem jest użycie Collectors.toList:

targetLongList = 
    sourceLongList.stream().
    filter(l -> l > 100).
    collect(Collectors.toList());

Poprzednie rozwiązanie:

Innym podejściem jest użycie Collectors.toCollection:

targetLongList = 
    sourceLongList.stream().
    filter(l -> l > 100).
    collect(Collectors.toCollection(ArrayList::new));
MohamedSanaulla
źródło
60
Jest to jednak przydatne, jeśli chcesz mieć konkretną implementację listy.
orbfish
1
Pomimo tego, że zaleca się kodowanie za pomocą interfejsów, istnieją wyraźne przypadki (jednym z nich jest GWT), gdy trzeba kodować za pomocą konkretnych implementacji (chyba że chcesz, aby wszystkie implementacje List zostały skompilowane i dostarczone jako javascript).
Edward Korenschi
3
Kolejny pro dla tej metody, z Collectors::toListjavadoc: „Nie ma żadnych gwarancji na rodzaj, zmienność, serializację lub bezpieczeństwo wątków zwróconej listy; jeśli wymagana jest większa kontrola nad zwracaną listą, użyj toCollection(Supplier)”.
Charles Wood,
12

Lubię używać metody util, która zwraca kolektor, ArrayListgdy tego właśnie chcę.

Myślę, że użycie tego rozwiązania Collectors.toCollection(ArrayList::new)jest trochę zbyt głośne w przypadku tak powszechnej operacji.

Przykład:

ArrayList<Long> result = sourceLongList.stream()
    .filter(l -> l > 100)
    .collect(toArrayList());

public static <T> Collector<T, ?, ArrayList<T>> toArrayList() {
    return Collectors.toCollection(ArrayList::new);
}

Tą odpowiedzią chcę również zademonstrować, jak łatwo jest tworzyć niestandardowe kolektory i korzystać z nich, co jest ogólnie bardzo przydatne.

Lii
źródło
Jeśli zadeklarujesz wynik jako List <Long>, nie musisz używać tej metody util. Collectors.toList zrobi. Ponadto używanie określonych klas zamiast interfejsów to zapach kodu.
Lluis Martinez
1
@LluisMartinez: „Collectors.toList zrobi”. : Nie, nie w wielu sytuacjach. Ponieważ nie jest dobrym pomysłem, toListjeśli chcesz na przykład zmodyfikować listę później w programie. toListDokumentacja mówi tak: „Nie ma żadnych gwarancji co do rodzaju, zmienności, serializability lub nitki bezpieczeństwa listy zwrócony, jeśli potrzebna jest większa kontrola nad zwróconej listy, użyj toCollection”. . Moja odpowiedź pokazuje sposób, aby uczynić to wygodniejszym w zwykłym przypadku.
Lii
Jeśli chcesz specjalnie utworzyć ArrayList, to jest w porządku.
Lluis Martinez,
5

Jeśli masz tablicę prymitywów, możesz użyć pierwotnych kolekcji dostępnych w Eclipse Collections .

LongList sourceLongList = LongLists.mutable.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
LongList targetLongList = sourceLongList.select(l -> l > 100);

Jeśli nie możesz zmienić sourceLongList z List:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = 
    ListAdapter.adapt(sourceLongList).select(l -> l > 100, new ArrayList<>());

Jeśli chcesz użyć LongStream:

long[] sourceLongs = new long[]{1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L};
LongList targetList = 
    LongStream.of(sourceLongs)
    .filter(l -> l > 100)
    .collect(LongArrayList::new, LongArrayList::add, LongArrayList::addAll);

Uwaga: Jestem współtwórcą kolekcji Eclipse.

Nikhil Nanivadekar
źródło
4

Trochę bardziej wydajny sposób (unikaj tworzenia listy źródłowej i automatycznego rozpakowywania przez filtr):

List<Long> targetLongList = LongStream.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L)
    .filter(l -> l > 100)
    .boxed()
    .collect(Collectors.toList());
msayag
źródło
1

Jeśli nie przeszkadza przy użyciu bibliotek 3rd party, AOL Cyklop reagują lib (ujawnienie jestem specjalista) posiada rozszerzenia dla wszystkich JDK Kolekcja typów, w tym listy . Interfejs ListX rozszerza java.util.List i dodaje wiele przydatnych operatorów, w tym filtr.

Możesz po prostu napisać

ListX<Long> sourceLongList = ListX.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
ListX<Long> targetLongList = sourceLongList.filter(l -> l > 100);

ListX można również utworzyć z istniejącej listy (poprzez ListX.fromIterable)

John McClean
źródło
1

Istnieje inny wariant metody kolekcjonowania zapewniany przez klasę LongStream, podobnie jak klasy IntStream i DoubleStream.

<R> R collect(Supplier<R> supplier,
              ObjLongConsumer<R> accumulator,
              BiConsumer<R,R> combiner)

Wykonuje zmienną operację redukcji na elementach tego strumienia. Zmienna redukcja to taka, w której zmniejszona wartość jest zmiennym pojemnikiem wyników, takim jak ArrayList, a elementy są włączane przez aktualizację stanu wyniku zamiast zastępowania wyniku. Daje to wynik równoważny z:

R result = supplier.get();
  for (long element : this stream)
       accumulator.accept(result, element);
  return result;

Podobnie jak redukcja (long, LongBinaryOperator), operacje zbierania mogą być równoległe bez konieczności dodatkowej synchronizacji. To jest operacja terminalowa.

Odpowiedź na twoje pytanie za pomocą tej metody zbierania jest następująca:

    LongStream.of(1L, 2L, 3L, 3L).filter(i -> i > 2)
    .collect(ArrayList::new, (list, value) -> list.add(value)
    , (list1, list2) -> list1.addAll(list2));

Poniżej znajduje się wariant referencyjny metody, który jest dość sprytny, ale niektóre trudne do zrozumienia:

     LongStream.of(1L, 2L, 3L, 3L).filter(i -> i > 2)
    .collect(ArrayList::new, List::add , List::addAll);

Poniżej będzie wariant HashSet:

     LongStream.of(1L, 2L, 3L, 3).filter(i -> i > 2)
     .collect(HashSet::new, HashSet::add, HashSet::addAll);

Podobnie wariant LinkedList wygląda następująco:

     LongStream.of(1L, 2L, 3L, 3L)
     .filter(i -> i > 2)
     .collect(LinkedList::new, LinkedList::add, LinkedList::addAll);
Vaneet Kataria
źródło
0

Jeśli ktoś (jak ja) szuka sposobów radzenia sobie z obiektami zamiast typów pierwotnych, użyj mapToObj()

String ss = "An alternative way is to insert the following VM option before "
        + "the -vmargs option in the Eclipse shortcut properties(edit the "
        + "field Target inside the Shortcut tab):";

List<Character> ll = ss
                        .chars()
                        .mapToObj(c -> new Character((char) c))
                        .collect(Collectors.toList());

System.out.println("List type: " + ll.getClass());
System.out.println("Elem type: " + ll.get(0).getClass());
ll.stream().limit(50).forEach(System.out::print);

drukuje:

List type: class java.util.ArrayList
Elem type: class java.lang.Character
An alternative way is to insert the following VM o
Kashyap
źródło
0
String joined = 
                Stream.of(isRead?"read":"", isFlagged?"flagged":"", isActionRequired?"action":"", isHide?"hide":"")
                      .filter(s -> s != null && !s.isEmpty())
                      .collect(Collectors.joining(","));
sharath
źródło
0

Oto kod AbacusUtil

LongStream.of(1, 10, 50, 80, 100, 120, 133, 333).filter(e -> e > 100).toList();

Ujawnienie : Jestem programistą AbacusUtil.

użytkownik_3380739
źródło
Nie znajduję żadnej metody toList obecnej w klasie LongStream. Czy możesz uruchomić ten kod?
Vaneet Kataria
@VaneetKataria try com.landawn.abacus.util.stream.LongStreamor LongStreamExin AbacusUtil
user_3380739
0

Możesz przepisać kod jak poniżej:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = sourceLongList.stream().filter(l -> l > 100).collect(Collectors.toList());
Pratik Pawar
źródło
Dzięki za wkład. Wyjaśnij jednak, co zmieniłeś i jak bardzo odnosi się to do pytania.
B - rian
Tutaj po pierwsze przekonwertowałem moją ArrayList na parę za pomocą filtru Odfiltrowuję wymagane dane. Wreszcie użyłem metody zbierania strumienia java 8 do gromadzenia danych na nowej liście o nazwie targetLongList.
Pratik Pawar
0

Aby zebrać na zmiennej liście:

targetList = sourceList.stream()
                       .filter(i -> i > 100) //apply filter
                       .collect(Collectors.toList());

Aby zebrać na niezmiennej liście:

targetList = sourceList.stream()
                       .filter(i -> i > 100) //apply filter
                       .collect(Collectors.toUnmodifiableList());

Objaśnienie collectz JavaDoc :

Wykonuje zmienną operację redukcji elementów tego strumienia za pomocą Collectora. Collector hermetyzuje funkcje używane jako argumenty do zbierania (dostawca, BiConsumer, BiConsumer), pozwalając na ponowne użycie strategii zbierania i składanie operacji zbierania, takich jak grupowanie lub partycjonowanie na wielu poziomach. Jeśli strumień jest równoległy, a moduł zbierający jest współbieżny, a albo strumień jest nieuporządkowany, albo moduł zbierający jest nieuporządkowany, wówczas zostanie wykonana równoczesna redukcja (szczegółowe informacje na temat redukcji współbieżnej można znaleźć w sekcji Kolektor).

To jest operacja terminalowa.

Podczas wykonywania równoległego wiele wyników pośrednich może być utworzonych, wypełnionych i scalonych, aby zachować izolację zmiennych danych. Dlatego nawet przy równoległym wykonywaniu z niechronionymi wątkami strukturami danych (takimi jak ArrayList), nie jest wymagana dodatkowa synchronizacja dla równoległej redukcji.

Saikat
źródło
-3

Jeśli nie użyjesz, parallel()to zadziała

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);

List<Long> targetLongList =  new ArrayList<Long>();

sourceLongList.stream().peek(i->targetLongList.add(i)).collect(Collectors.toList());
Debapriya Biswas
źródło
Nie podoba mi się, że funkcja collect () służy wyłącznie do sterowania strumieniem, dlatego haczyk peek () jest wywoływany dla każdego elementu. Wynik działania terminalu jest odrzucany.
Daniel K.,
Dziwnie jest zadzwonić, collecta następnie nie zapisać wartości zwracanej. W takim przypadku możesz użyć forEachzamiast tego. Ale to wciąż złe rozwiązanie.
Lii
1
Używanie peek () w ten sposób jest antypatternem.
Grzegorz Piwowarek,
Zgodnie z Stream Java docs metoda peek musi być używana wyłącznie do celów debugowania. Nie powinna być używana do przetwarzania innego niż debugowanie.
Vaneet Kataria