Describe the Stream interface and pipelines; create a stream by using the Arrays.stream() and IntStream.range() methods; identify the lambda operations that are lazy.
One of the biggest things about Java 8 is Streams. A Stream is a data stream. The data can be Strings, numbers, or any other object. This data goes through a series of operations, and the set of these operations is called pipeline. Lambda expressions almost always represent these operations. So it is essential to have mastered the whole chapter on lambda, as all those concepts will now be used to form a Stream.
From the following examples, this explanation will become more evident.
Typically, a Stream is created from a dataset, such as a list or other collection type. The purpose of the certification makes explicit that it is necessary to know the Arrays.stream() and IntStream.range() methods. But in addition to these, some other common ways to create a Stream will be presented.
-
You can create a stream from an
Arrayusing theArrays.stream()method.src/org/j6toj8/streams/usingstreams/Stream_ArraysStream.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_ArraysStream.java[role=include]
console outputA B C
-
You can create a stream from a number range using the
IntStream.range()method.src/org/j6toj8/streams/usingstreams/Stream_IntRangeStream.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_IntRangeStream.java[role=include]
console output0 1 2 3
Note that the first argument (number 0) is inclusive, while the second argument (number 4) is exclusive. Therefore the console output has only the numbers 0 to 3.
-
You can create a Stream from a list.
src/org/j6toj8/streams/usingstreams/Streams_ListStream.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_ListStream.java[role=include]
console outputA B C
-
You can create a Stream from specific elements using the
Stream.ofmethod.src/org/j6toj8/streams/usingstreams/Streams_Of.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_Of.java[role=include]
console outputA B 1 2 3.0 4.0
In this case a Stream was created that contains:
String,Character,Integer,Long,FloatandDouble.
Operations done on a Stream will form its pipeline. Operations that can be performed on a Stream are divided into Intermediate Operations and Final Operations. Stream can contain numerous intermediate operations, but only one final operation. In the previous examples the only operation used was forEach, which is a final operation. Other operations will be presented below.
-
You can ignore elements of a stream with the
skipoperation.src/org/j6toj8/streams/usingstreams/Stream_Skip.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_Skip.java[role=include]
console output2 3
Note that in this case elements 0 and 1 were ignored, as they are the first two elements of Stream. This was due to the existence of the
skipoperation. -
You can limit the amount of elements that will be processed using the
limitoperation.src/org/j6toj8/streams/usingstreams/Streams_Limit.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_Limit.java[role=include]
console output0 1
In this case only the first 2 elements were printed on the console because the
limitoperation limited the amount of elements to be processed. -
You can filter
Streamelements using thefilteroperation.src/org/j6toj8/streams/usingstreams/Streams_Filter.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_Filter.java[role=include]
console output0 2
In this case only the even elements were printed, as the
filteroperation limited to those that have remainder division by 2 equal to 0. -
You can filter out repeated Stream elements using the
distinctoperation.src/org/j6toj8/streams/usingstreams/Streams_Distinct.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_Distinct.java[role=include]
console outputA B C F
Note that in this case the repeated stream elements (
"A"and"B") were ignored and only presented once.The
distinctoperation uses theequalsandhashCodemethods, so make sure they are implemented correctly if you are using an object type that you create. In the example were used objects of typeString, which already have this implementation by default. -
You can apply a transformation to Stream elements using the
mapoperation.src/org/j6toj8/streams/usingstreams/Streams_Map.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_Map.java[role=include]
console output0 2 4 6
Note that in this case, the elements underwent a transformation, which was multiplication by 2 before being printed on the console.
-
You can sort the elements of a Stream using the
sortedoperation.src/org/j6toj8/streams/usingstreams/Streams_Sorted.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_Sorted.java[role=include]
console outputA A B B C F G T Y
In this case, all elements are sorted using the natural order of the
Stringobjects, as they already implement theComparableinterface and are presented in alphabetical order. There is also a version of thesortmethod that takes as its argument an implementation ofComparatorif you want to sort it otherwise. -
You can observe the elements passing through a Stream using the
peekoperation.src/org/j6toj8/streams/usingstreams/Streams_Peek.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_Peek.java[role=include]
console outputPeek: G ForEach: G Peek: T ForEach: T Peek: Y ForEach: Y Peek: A ForEach: A
The
peekoperation works only to observe what is going through Stream. It can be very useful for performing debug or log. In this case, the elements are being printed twice on the console because thepeekandforEachmethods are both doing the same action. However, in real applications, usually the final operation will not be aforEach, so it will make sense to usepeek. -
You can transform a Stream from multiple
Arraysinto a single continuous Stream using theflatMapmethod.src/org/j6toj8/streams/usingstreams/Streams_FlatMap.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_FlatMap.java[role=include]
console outputA B C D E F G H I
Note that in this case there are 3 distinct
Arrays. Then create a Stream containing 3Arrays. The typical scenario would be that each Stream element was anArrayobject. However, using theflatMapoperation, a Stream is created for each of theseArrays, which are joined and form a single continuous Stream.
-
You can perform a final action for each Stream element using the
forEachoperation, as shown in the previous examples. -
You can retrieve the largest and smallest value of a Stream using the
maxandminfinal operations. And it is also possible to retrieve the number of elements of a Stream using thecountfinal operation.src/org/j6toj8/streams/usingstreams/Streams_MaxMinCount.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_MaxMinCount.java[role=include]
console outputMax: 9 Min: 1 Count: 8
For
maxandminoperations, it is necessary to pass as an argument which comparator will be used. Since numbers have a natural order, that is, they implement theComparableinterface, it is possible to use a comparator that uses that natural order, which isComparator.naturalOrder(). If it is a type of object that does not have a natural order, it is necessary to pass another implementation ofComparatoras an argument.The
maxandminoperations returnOptionalbecause if Stream is empty it will be emptyOptional. Since Java 8, with the addition of theOptionalclass, this has been preferred over returningnullas it facilitates functional programming. Thecountoperation does not need anOptional, because even with an empty Stream will return0. -
You can get the first element of the Stream using the final
findFirstoperation, or any element withfindAny.src/org/j6toj8/streams/usingstreams/Streams_FindFirstAny.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_FindFirstAny.java[role=include]
console outputFirst: 7 Any: 7
In this case, because Stream is sequential and not parallel, both results are equal. In parallel Streams, which will be presented in another section, the
findAnyoperation may yield different results.Like the
maxandminoperations presented earlier,findAnyandfindFirstreturn an emptyOptionalif Stream is empty. -
You can verify that Stream elements meet some validation by using the
allMatch,anyMatch, andnoneMatchfinal operations.src/org/j6toj8/streams/usingstreams/Streams_Match.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_Match.java[role=include]
console outputanyMatch: true allMatch: false noneMatch: false
Note that in the first operation it is checked that any element is greater than 5. In the second, if all elements are greater than 5. And in the third, if no element is greater than 5.
-
You cannot call more than one final operation on the same Stream.
src/org/j6toj8/streams/usingstreams/Streams_ReuseStream.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_ReuseStream.java[role=include]
console output7 2 1 Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279) at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) at org.j6toj8.streams.usingstreams.Streams_ReuseStream.main(Streams_ReuseStream.java:11)
-
You can create a pipeline with multiple operations in a single Stream.
src/org/j6toj8/streams/usingstreams/Streams_Pipeline.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_Pipeline.java[role=include]
console output8 12
To understand all operations performed in this pipeline, you must understand step by step:
-
A Stream was created containing all numbers from 0 to 9.
-
A filter was applied keeping only the even numbers: 0, 2, 4, 6 and 8.
-
The first two numbers were ignored, keeping only: 4, 6 and 8.
-
Processing was limited to the first two numbers: 4 and 6.
-
Multiplication by 2 was applied to each element, resulting in 8 and 12.
-
Both elements were printed on the console.
-
-
The Stream will only actually be created after any operation has been performed on it.
src/org/j6toj8/streams/usingstreams/Streams_ChangeBackingList.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_ChangeBackingList.java[role=include]
console output1 2 3 4
Note that even though Stream was apparently created before adding number 4 to the list, it prints this number on the console. This is because Stream was only created when some operation was done on it, i.e., when
forEachwas invoked. -
You can chain the final Stream operation using lambda expressions in the
Optionalclass.src/org/j6toj8/streams/usingstreams/Streams_Optional.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_Optional.java[role=include]
Note that the
ifPresentmethod is from theOptionalclass, even though in the second example, it might look like it is part of Stream. In other words, the final operation ismax, andifPresentis a called inOptionaland no longer in Stream
Intermediate operations of a Stream are only performed when necessary. That is, even if the operation is present in pipeline, it is not certain that it will be executed.
-
Nothing will be done if Stream does not contain a final operation.
src/org/j6toj8/streams/usingstreams/Streams_LazyNoFinal.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_LazyNoFinal.java[role=include]
In this case nothing is printed on the console because no final operations were applied to Stream. That is, if there is nothing consuming the result of this Stream, Java does not have to execute the created pipeline.
-
Other intermediate operations are also not usually performed if not required.
src/org/j6toj8/streams/usingstreams/Streams_LazyMap.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_LazyMap.java[role=include]
console outputPeek: 0 ForEach: 0 Peek: 1 ForEach: 1 Peek: 2 ForEach: 2
Note that even if the
peekoperation is before thelimitoperation, it is not performed for allStreamelements, only for those that will actually be used.
There are specific streams for some primitive types like double, int and long. They have the advantage of avoiding Boxing and Unboxing by providing some more specialized methods as shown below.
-
You can create Streams of primitive types with the classes:
DoubleStream,IntStreamandLongStream.src/org/j6toj8/streams/usingstreams/primitives/Streams_Primitives.javalink:../../../src/org/j6toj8/streams/usingstreams/primitives/Streams_Primitives.java[role=include]
console outputDoubleStream 1.12.23.3 IntStream 123 123 LongStream 123 123
-
You can transform a common Stream into a Stream of primitives using
mapTo*operations.src/org/j6toj8/streams/usingstreams/primitives/Streams_MapTo.javalink:../../../src/org/j6toj8/streams/usingstreams/primitives/Streams_MapTo.java[role=include]
console outputStream to IntStream 1234 Stream to LongStream 1234 Stream to DoubleStream 1.02.03.04.0
-
You can generate infinite Streams with the
generatemethod.src/org/j6toj8/streams/usingstreams/primitives/Streams_Generate.javalink:../../../src/org/j6toj8/streams/usingstreams/primitives/Streams_Generate.java[role=include]
console outputinfinity IntStream of random numbers 2111846625 -1692075394 122693397 infinity DoubleStream of random numbers 0.913037010633669 0.23669861350384735 0.32655918031847697
In this case the Streams are really infinite. Only 3 numbers of each were displayed as there is the
limitoperation, otherwise program execution would also be endless. -
You can use the
rangeClosedoperation instead ofrange, making the code more readable.src/org/j6toj8/streams/usingstreams/primitives/Streams_RangeClosed.javalink:../../../src/org/j6toj8/streams/usingstreams/primitives/Streams_RangeClosed.java[role=include]
console output123 1234
Note that in the call using
range, the last number is unique (not part of Stream). InrangeClosed, both the first and last numbers are inclusive (they are part of Stream). -
You can generate various Streams statistics using the
summaryStatisticsoperation.src/org/j6toj8/streams/usingstreams/primitives/Streams_Statistics.javalink:../../../src/org/j6toj8/streams/usingstreams/primitives/Streams_Statistics.java[role=include]
console outputCount: 10 Max: 9 Min: 0 Sum: 45 Average: 4.5
Reduce is one of the main final operations that can be done on a Stream. It is an operation that transforms multiple Stream values into a single value. Several operations previously presented are a type of Reduce, such as: max, min and summaryStatistics. However, these operations are not always sufficient, so there are reduce methods. They allow the implementation of Reduce custom operations.
-
You can create a custom Reduce operation with the
reduce()method that takes 1 argument.src/org/j6toj8/streams/usingstreams/primitives/Streams_Reduce.javalink:../../../src/org/j6toj8/streams/usingstreams/reduce/Streams_Reduce.java[role=include]
console output336In this case a Reduce is being made where the result of the previous operation is passed to the next execution. That is, first we multiply 7 * 2, which is 14. Then the function is called again passing as argument the previous result (14) and the next Stream number (3). The result is 42. Then the function is called one last time passing the previous result (42) and the next Stream number (8), which gives the result of 336.
-
You can create a Reduce operation by entering the identity value.
src/org/j6toj8/streams/usingstreams/primitives/Streams_ReduceIdentity.javalink:../../../src/org/j6toj8/streams/usingstreams/reduce/Streams_ReduceIdentity.java[role=include]
console output336In this case you can enter the identity value of the role. The concept of value or identity function is a bit more complex, but for certification just understand that it represents a neutral value. That is, for the multiplication operation, the identity value is 1, because any value multiplied by 1 results in itself. If it were a sum operation, the identity value would be 0 because any value added to 0 results in itself.
Also, if Stream is empty, the identity value is returned. Therefore, unlike the previous example, it is not necessary to return an
Optional. -
You can create a Reduce operation that can be performed on multiple Threads and then combined into a single value.
src/org/j6toj8/streams/usingstreams/primitives/Streams_ReduceCombiner.javalink:../../../src/org/j6toj8/streams/usingstreams/reduce/Streams_ReduceCombiner.java[role=include]
console output336In this case, an additional argument is passed. It is the combination function. This function is used when Stream is parallel, in other words, it uses more than one thread. It takes the value returned by 2 or more threads and combines them into a single value. In a multiplication operation, the combination is also a multiplication. That is, if the first thread multiplies 7 and 2, resulting in 14, and the second thread multiplies 3 and 8, resulting in 24, the combination function only needs to multiply 14 by 24 to reach the value of 336. Thus, the function combination only makes sense in a parallel Stream, which will be presented in the next chapter.
The collect final operation is also a type of Reduce but is used for mutable objects. That is, instead of using the reduce operation with String, it would probably be more efficient to use the collect operation with the StringBuilder class to avoid creating multiple String objects. Because Java uses many mutable objects, including lists and maps, generally the collect operation will be more efficient than reduce.
Because they are very common, there are several Collectors already implemented in Java, available in the Collectors class.
-
It is possible to use a
Collectorthat joins severalStrings.src/org/j6toj8/streams/usingstreams/primitives/Streams_CollectorJoining.javalink:../../../src/org/j6toj8/streams/usingstreams/collect/Streams_CollectorJoining.java[role=include]
console outputABC -
You can use a
Collectorto represent each element as a number and average them.src/org/j6toj8/streams/usingstreams/primitives/Streams_CollectorAveragingInt.javalink:../../../src/org/j6toj8/streams/usingstreams/collect/Streams_CollectorAveragingInt.java[role=include]
console output6.2 -
You can use a
Collectorto store the elements of a Stream in a new collection.src/org/j6toj8/streams/usingstreams/primitives/Streams_CollectorToCollect.javalink:../../../src/org/j6toj8/streams/usingstreams/collect/Streams_CollectorToCollect.java[role=include]
console outputArrayList: [1, 2, 3, 4] HashSet: [1, 2, 3, 4] LinkedList: [1, 2, 3, 4] TreeSet: [1, 2, 3, 4]
-
You can use a
Collectorto store the elements of a Stream in a map.src/org/j6toj8/streams/usingstreams/primitives/Streams_CollectorToMap.javalink:../../../src/org/j6toj8/streams/usingstreams/collect/Streams_CollectorToMap.java[role=include]
console output{Roseany=7, Amélia=6, Rodrigo=7, Rinaldo=7, Luiz=4} -
It is also possible to store in a map in cases where the key will be repeated. The third argument of the
toMapmethod defines the rule to merge values to equal keys.src/org/j6toj8/streams/usingstreams/primitives/Streams_CollectorToMapDuplicateKey.javalink:../../../src/org/j6toj8/streams/usingstreams/collect/Streams_CollectorToMapDuplicateKey.java[role=include]
console output{4=Luiz, 6=Amélia, 7=Rinaldo,Rodrigo,Roseany} -
You can use a
Collectorthat creates a map by grouping values that have the same key in a list.src/org/j6toj8/streams/usingstreams/primitives/Streams_CollectorGroupingBy.javalink:../../../src/org/j6toj8/streams/usingstreams/collect/Streams_CollectorGroupingBy.java[role=include]
console output{4=[Luiz], 6=[Amélia], 7=[Rinaldo, Rodrigo, Roseany]} -
You can also customize the way that values with equal keys will be combined.
src/org/j6toj8/streams/usingstreams/primitives/Streams_CollectorGroupingByDownstream.javalink:../../../src/org/j6toj8/streams/usingstreams/collect/Streams_CollectorGroupingByDownstream.java[role=include]
console output{4=Luiz, 6=Amélia, 7=Rinaldo,Rodrigo,Roseany}Note that in this case the values were combined using another Collector, which grouped the names separating with commas.
-
You can also define which type of map will be used for grouping.
src/org/j6toj8/streams/usingstreams/primitives/Streams_CollectorGroupingByMapFactory.javalink:../../../src/org/j6toj8/streams/usingstreams/collect/Streams_CollectorGroupingByMapFactory.java[role=include]
console output{4=Luiz, 6=Amélia, 7=Rinaldo,Rodrigo,Roseany}Note that the result of this example is identical to the previous one, but one more argument was passed, which is the map constructor that should be used.
-
You can use a
Collectorthat partitions values inTrueorFalsefrom a function of typePredicate.src/org/j6toj8/streams/usingstreams/primitives/Streams_CollectorPartitioningBy.javalink:../../../src/org/j6toj8/streams/usingstreams/collect/Streams_CollectorPartitioningBy.java[role=include]
console output{false=[Luiz, Amélia], true=[Rinaldo, Rodrigo, Roseany]}Note that in this case the partitioning rule is the names beginning with
R. -
You can also customize how the combination of partitioned values will be done.
src/org/j6toj8/streams/usingstreams/primitives/Streams_CollectorPartitioningByDownstream.javalink:../../../src/org/j6toj8/streams/usingstreams/collect/Streams_CollectorPartitioningByDownstream.java[role=include]
console output{false=Luiz,Amélia, true=Rinaldo,Rodrigo,Roseany}Note that in this case the values were combined using another
Collector, which merged the values of that same key into a singleStringseparated by commas. -
You can add one more layer of transformation using a
Collectorusing themappingmethod.src/org/j6toj8/streams/usingstreams/primitives/Streams_CollectorMapping.javalink:../../../src/org/j6toj8/streams/usingstreams/collect/Streams_CollectorMapping.java[role=include]
console output{4=LUIZ, 6=AMÉLIA, 7=RINALDO,RODRIGO,ROSEANY}This type of code, while complex, may appear on the certification exam. It is recommended to practice these examples with an IDE to really understand their behaviors. Access the sample codes in this book to facilitate your study.
-
Using Streams
Boyarsky, Jeanne; Selikoff, Scott. OCP: Oracle Certified Professional Java SE 8 Programmer II Study Guide (p. 185). Wiley. Kindle Edition.
-
Lesson: Aggregate Operations. The Java™ Tutorials.
-
Package java.util.stream. Java Plataform SE 8.
-
Interface Stream<T>. Java Plataform SE 8.