Java-stream List to String
IntroductionStreams don't hold any data by themselves - they just stream it from a source. Yet, common code routines expect some sort of a structure to hold results after processing data. That is why, after (optional) intermediate operations, the Stream API provides for ways to convert the elements that it may have acted on into collections - like lists, that you can further use in your code. Show
These ways include applying:
Or, you could terminate a stream by turning it into an array. Then, turn that array into a list. This is because the API already has two methods for producing arrays. They include: Object[] toArray();A[] toArray(IntFunction generator);
These array-producing methods are bound to make code extra verbose. And, that may make your code less readable. Yet in the end, they will still help you to convert a stream to a list.
Otherwise, this guide will look into how all these approaches work in detail. It will also throw in a few dirty hacks which will help you to convert too. Be careful with them, though - such tinkerings are bound to hurt your code's performance. How to Convert a Stream to List using CollectorsThe official documentation defines a collector as an implementation which is:
And:
Note how these 4 conditions seem like a mouthful. But, as we will see next, they are not as hard to fulfill. Pre-defined CollectorsThe Java 8 Stream API works in tandem with the Collectors API. The Collectors class offers ready-made collectors that apply the supplier-accumulator-combiner in their implementations. Hence, using facilities from the Collectors utility class will clean up your code significantly.
To convert a stream into a list using pre-built Collectors, we simply collect() it into a list: List list = Stream.of("David", "Scott", "Hiram").collect(Collectors.toList()); System.out.println(String.format("Class: %s\nList: %s", list.getClass(), list));This example is rather simple and just deals with Strings: Class: class java.util.ArrayList List: [David, Scott, Hiram]Though, if you're not working with Strings or simpler types, you'll likely have to map() your objects before collecting them, which is more often the case than not. Let's define a simple Donor object, and a BloodBank that keeps track of them, and convert a Stream of Donors into a List. Convert Stream to List with map() and collect()Let us start by declaring a Donor class to model a blood donor: It is advisable to implement the Comparable interface here since it facilitates the ordering and sorting of the Donor objects in collections. You can always supply custom Comparators instead, though, a Comparable entity is simply easier and cleaner to work with. Then, we define a BloodBank interface, which specifies that blood banks can receive a donation from a Donor, as well as return all the available types: public interface BloodBank { void receiveDonationFrom(Donor donor); List getAvailableTypes(); }The next step is to create a concrete implementation of a BloodBank. Since all concrete implementations will accept donors, and only the approach to getting the available types will be implementation-dependent - let's create an abstract class as a middleman: Finally, we can go ahead and create a concrete implementation and map() the Donor list to their blood type, within a Stream and collect() it back into a list, returning the available blood types: public class CollectorsBloodBank extends AbstractBloodBank { @Override public List getAvailableTypes() { return donors.stream().map(Donor::getBloodGroup).collect(Collectors.toList()); } }You can map() the donors to any of the fields in the object and return a list of those fields, such as the amountDonated or name as well. Having a comparable field also makes it possible to sort them via sorted().
You could return all of the Donor instances instead, by simply calling collect() on their Stream: @Override public List getAvailableDonors() { return donors.stream().collect(Collectors.toList()); }Though, you're not limited to just collecting a stream into a list - this is where the collectingAndThen() method comes into play. Convert Stream to List with Collectors.collectingAndThen()Earlier on we consulted the official documentation and, it stated that collectors have the capacity of:
The accumulated result in CollectorsBloodBank, for example, is represented by Collectors.toList(). We can transform this result further using the method Collectors.collectingAndThen(). Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it! Good practice requires one to return immutable collection objects. So, if we were to stick to this practice, a finisher step can be added to the conversion of stream to list: Alternatively, you can put any Function as a finisher here as well.
Convert Stream to List with Suppliers, Accumulators, and CombinersInstead of using pre-defined collectors, you can use separate Suppliers, Accumulators and Combiners instead. These are implemented as a Suplier, BiConsumer and BiConsumer, which all fit snuggly into a collect() instead of a pre-defined Collector. Let's take a look at how you can utilize this flexibility to return all the available types: The implementation above applies the requisite supplier-accumulator-combiner pattern in a few steps: Firstly, it turns the donors list field into a stream of Donor elements.
Then, an intermediate map operation is performed on the stream of Donors. The operation creates a new stream containing the String values that represent the donors' blood group types. Then. a result container that is mutable - i.e., the collector's supplier is created. This supplier container will be henceforth known as bloodGroups. We add each blood group type (named bloodgroup in this step) from the stream into the mutable container: bloodGroups. In other words, the accumulation is occurring at this step. The mutable, supplier container bloodGroups is added into the result container known as the resultList in this step. This is thus the combiner step. We can improve the LambdaBloodBank's getAvailableTypes() method further by using method references instead of lambdas: public class MembersBloodBank extends AbstractBloodBank { @Override public List getAvailableTypes() { return donors.stream() .map(Donor::getBloodGroup) .collect( ArrayList::new, ArrayList::add, ArrayList::addAll ); } }Creating Custom Collectors for Java 8 StreamsWhen you pass: You are providing the arguments that the Collectors utility class will use to create a custom collector for you, implicitly. Otherwise, the starting point for creating a custom collector is the implementation of the Collector interface. In our case, a collector that accumulates the blood group types would look like this CustomCollector class: The CustomCollector class can then help you to convert a stream to a list like in this CustomCollectorBloodBank class: Note: If you were to go all out with this - you can have multiple methods, such as toList(), toMap(), etc. that return different collections, using this same class. How to Convert a Stream to List using ArraysThe Stream API offers a way of collecting elements from a stream pipeline into arrays. And because the Arrays utility class has methods that transform arrays into lists, this is a route you can opt for. Albeit, this approach is verbose, code-wise, and it's recommended to utilize either pre-built collectors, or to define your own if the standard ones don't fit your use-case. Arrays of ObjectsUsing the Stream.toArray() method, transform a stream into an array of objects. (That is, elements of the base Object class). This may turn too verbose, depending on your use case and, it risks lowering your code's readability to a considerable extent. Take this ArrayOfObjectsBloodBank class, for example: This approach is fickle, requires classic for loops and iteration, manual casting and is considerably less readable than previous approaches - but it works. Arrays Requiring an IntFunction GeneratorAnother way that the Stream API offers for turning a stream of elements into an array is the Stream.toArray(IntFunction generator) method. Whereas the preceding tactic of deriving an array of objects demanded the use of considerably many lines of code, the generator approach is quite succinct: This is much better than the previous approach, and actually isn't all that bad - though, there's still a simply redundant conversion between an array and list here. Other (Discouraged) Tactics of Converting Streams to ListsThe Stream API discourages the introduction of side effects into the stream pipeline. Because streams may be exposed to parallel threads, it is dangerous to attempt to modify an externally declared source container. Thus, the two following examples of using Stream.forEach() and Stream.reduce() when you want to convert a stream to list are bad hacks. Piggybacking on Stream.forEach()Without parallelism, this works just fine and the code will produce the results you want but it's not future-proof and is best avoided. Convert a Stream to List using Stream.reduce()ConclusionThe Stream API introduced multiple ways of making Java more functional in nature. Because streams help operations to run in parallel, it is important that optional intermediate and terminal operations uphold the principles of:
Among the tactics that this article has explored, the use of collectors is the one that promises to help you achieve all the three principles. It is thus important that as you continue working with streams, you improve your skills of handling both pre-defined and custom collectors. The source code for this guide is available on GitHub. |