Retrieving a List from a java.util.stream.Stream in Java 8
Asked Answered
S

15

502

I was playing around with Java 8 lambdas to easily filter collections. But I did not find a concise way to retrieve the result as a new list within the same statement. Here is my most concise approach so far:

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);

Examples on the net did not answer my question because they stop without generating a new result list. There must be a more concise way. I would have expected, that the Stream class has methods as toList(), toSet(), …

Is there a way that the variables targetLongList can be directly be assigned by the third line?

Steroid answered 12/2, 2013 at 10:26 Comment(2)
In case you don’t need the sourceLongList afterwards there’s Collection.removeIf(…) for convenience.Cryptography
How about this? List<Long> targetLongList = sourceLongList.stream().collect(Collectors.toList());Landy
G
669

What you are doing may be the simplest way, provided your stream stays sequential—otherwise you will have to put a call to sequential() before forEach.

The reason the call to sequential() is necessary is that the code as it stands (forEach(targetLongList::add)) would be racy if the stream was parallel. Even then, it will not achieve the effect intended, as forEach is explicitly nondeterministic—even in a sequential stream the order of element processing is not guaranteed. You would have to use forEachOrdered to ensure correct ordering. The intention of the Stream API designers is that you will use collector in this situation, as below:

targetLongList = sourceLongList.stream()
    .filter(l -> l > 100)
    .collect(Collectors.toList());
Greathouse answered 12/2, 2013 at 12:22 Comment(6)
Addition: I think this codes gets a little shorter, clearer and prettier if you use a static import of toList. This is done by placing the following among the imports of the file: static import java.util.stream.Collectors.toList;. Then the collect call reads just .collect(toList()).Decurion
In Eclipse it is possible to make the IDE add a static import for methods. This is done by adding the Collectors class in Preferences -> Java -> Editor -> Content Assist -> Favorites. After this, you only have to type toLi at hit Ctr+Space to have the IDE fill in toList and add the static import.Decurion
One thing to keep in mind is that IntStream and some other almost-but-not-quite-Streams do not have the collect(Collector) method and you will have to call IntStream.boxed() to convert them to a regular Stream first. Then again, maybe you just want toArray() .Orthopter
why we have to use sequential() before forEach or use 'forEachOrdered`Snowbird
@amarnathharish Because forEach doesn't guarantee the order of operation execution for a parallel stream. The JavaDoc says "The behavior of this operation is explicitly nondeterministic. For parallel stream pipelines, this operation does not guarantee to respect the encounter order of the stream, as doing so would sacrifice the benefit of parallelism." (The first sentence of this quote actually means that order is not guaranteed for sequential streams either, although in practice it is preserved.)Greathouse
why cannot it be as simple as C# syntax: (some ienumerable).ToList()?Turley
P
195

Updated:

Another approach is to use Collectors.toList:

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

Previous Solution:

Another approach is to use Collectors.toCollection:

targetLongList = 
    sourceLongList.stream().
    filter(l -> l > 100).
    collect(Collectors.toCollection(ArrayList::new));
Pinson answered 17/3, 2013 at 6:28 Comment(3)
This is, however, useful if you want a particular List implementation.Deeannadeeanne
Despite beeing recommended to code against interfaces, there are clear cases (one of them being GWT) when you have to code against concrete implementations (unless you want all List implementations compiled and delivered as javascript).Verdie
Another pro for this method, from the Collectors::toList javadoc: "There are no guarantees on the type, mutability, serializability, or thread-safety of the List returned; if more control over the returned List is required, use toCollection(Supplier)."Ameliaamelie
S
14

There is a new method Stream.toList() in Java 16, which accumulates the stream elements into an unmodifiable list:

List<Long> targetLongList = sourceLongList
         .stream()
         .filter(l -> l > 100)
         .toList();
Stipple answered 4/1, 2021 at 8:36 Comment(0)
D
13

I like to use a util method that returns a collector for ArrayList when that is what I want.

I think the solution using Collectors.toCollection(ArrayList::new) is a little too noisy for such a common operation.

Example:

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

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

With this answer I also want to demonstrate how simple it is to create and use custom collectors, which is very useful generally.

Decurion answered 20/11, 2015 at 9:15 Comment(3)
If you declare result as List<Long> you don't need to use this util method. Collectors.toList will do. Also, using specific classes instead of interfaces is a code smell.Virgule
@LluisMartinez: "Collectors.toList will do.": No, not in many situations. Because it's not a good idea to use toList if you for example want to modify the list later in the program. The toList documentation says this: "There are no guarantees on the type, mutability, serializability, or thread-safety of the List returned; if more control over the returned List is required, use toCollection.". My answer demonstrates a way to make it more convenient to do that in a common case.Decurion
If you want to specifically create an ArrayList then it's ok.Virgule
T
8
collect(Collectors.toList());

This is the call which you can use to convert any Stream to List.

more concretely:

    List<String> myList = stream.collect(Collectors.toList()); 

from:

https://www.geeksforgeeks.org/collectors-tolist-method-in-java-with-examples/

Tally answered 18/9, 2017 at 10:3 Comment(0)
U
5

If you have an array of primitives, you can use the primitive collections available in Eclipse Collections.

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

If you can't change the sourceLongList from 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<>());

If you want to use 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);

Note: I am a contributor to Eclipse Collections.

Unfathomable answered 27/3, 2016 at 0:8 Comment(0)
B
5

A little more efficient way (avoid the creating the source List and the auto-unboxing by the filter):

List<Long> targetLongList = LongStream.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L)
    .filter(l -> l > 100)
    .boxed()
    .collect(Collectors.toList());
Billups answered 6/10, 2016 at 7:13 Comment(0)
M
2

To collect in a mutable list:

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

To collect in a immutable list:

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

Explanation of collect from the JavaDoc:

Performs a mutable reduction operation on the elements of this stream using a Collector. A Collector encapsulates the functions used as arguments to collect(Supplier, BiConsumer, BiConsumer), allowing for reuse of collection strategies and composition of collect operations such as multiple-level grouping or partitioning. If the stream is parallel, and the Collector is concurrent, and either the stream is unordered or the collector is unordered, then a concurrent reduction will be performed (see Collector for details on concurrent reduction.)

This is a terminal operation.

When executed in parallel, multiple intermediate results may be instantiated, populated, and merged so as to maintain isolation of mutable data structures. Therefore, even when executed in parallel with non-thread-safe data structures (such as ArrayList), no additional synchronization is needed for a parallel reduction.

Moyers answered 6/6, 2020 at 18:25 Comment(0)
C
1

If you don't mind using 3rd party libraries, AOL's cyclops-react lib (disclosure I am a contributor) has extensions for all JDK Collection types, including List. The ListX interface extends java.util.List and adds a large number of useful operators, including filter.

You can simply write-

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

ListX also can be created from an existing List (via ListX.fromIterable)

Cordwain answered 25/2, 2016 at 10:18 Comment(0)
H
1

There is an another variant of collect method provided by LongStream class and similarly by IntStream and DoubleStream classes too .

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

Performs a mutable reduction operation on the elements of this stream. A mutable reduction is one in which the reduced value is a mutable result container, such as an ArrayList, and elements are incorporated by updating the state of the result rather than by replacing the result. This produces a result equivalent to:

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

Like reduce(long, LongBinaryOperator), collect operations can be parallelized without requiring additional synchronization. This is a terminal operation.

And answer to your question with this collect method is as below :

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

Below is the method reference variant which is quite smart but some what tricky to understand :

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

Below will be the HashSet variant :

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

Similarly LinkedList variant is like this :

     LongStream.of(1L, 2L, 3L, 3L)
     .filter(i -> i > 2)
     .collect(LinkedList::new, LinkedList::add, LinkedList::addAll);
Hartzke answered 25/1, 2019 at 11:57 Comment(0)
S
0

In case someone (like me) out there is looking for ways deal with Objects instead of primitive types then use 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);

prints:

List type: class java.util.ArrayList
Elem type: class java.lang.Character
An alternative way is to insert the following VM o
Smoothspoken answered 2/10, 2015 at 15:42 Comment(0)
S
0

Here is code by abacus-common

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

Disclosure: I'm the developer of abacus-common.

Symposiarch answered 2/12, 2016 at 7:46 Comment(2)
I don't find any toList method present in LongStream class. Could you run this code ?Hartzke
@VaneetKataria try com.landawn.abacus.util.stream.LongStream or LongStreamEx in AbacusUtilSymposiarch
T
0
String joined = 
                Stream.of(isRead?"read":"", isFlagged?"flagged":"", isActionRequired?"action":"", isHide?"hide":"")
                      .filter(s -> s != null && !s.isEmpty())
                      .collect(Collectors.joining(","));
Thorlie answered 23/2, 2017 at 22:54 Comment(0)
H
0

You can rewrite code as below :

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());
Harrell answered 26/7, 2019 at 12:15 Comment(2)
Thanks for your input. However, please explain what you changed and in how far it relates to the question.Postgraduate
Here firstly I converted my ArrayList to steam them using filter I filter out required data. Finally I have used collect method of java 8 stream to collect data in new list called as targetLongList.Harrell
E
-3

If you don't use parallel() this will work

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());
Englert answered 12/8, 2016 at 7:4 Comment(4)
I don't like that the collect() is only used to drive the stream so that the peek() hook is called on each item. The result of the terminal operation is discarded.Steroid
It is very weird to call collect and then not save the return value. In that case you could use forEach instead. But that is still a poor solution.Decurion
Using peek() in this way is an antipattern.Gravois
As per Stream Java docs peek method must be used for debugging purposes only .It should not be used for any processing other than debugging .Hartzke

© 2022 - 2024 — McMap. All rights reserved.