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
Array
using 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.of
method.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
,Float
andDouble
.
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
skip
operation.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
skip
operation. -
You can limit the amount of elements that will be processed using the
limit
operation.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
limit
operation limited the amount of elements to be processed. -
You can filter
Stream
elements using thefilter
operation.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
filter
operation limited to those that have remainder division by 2 equal to 0. -
You can filter out repeated Stream elements using the
distinct
operation.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
distinct
operation uses theequals
andhashCode
methods, 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
map
operation.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
sorted
operation.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
String
objects, as they already implement theComparable
interface and are presented in alphabetical order. There is also a version of thesort
method that takes as its argument an implementation ofComparator
if you want to sort it otherwise. -
You can observe the elements passing through a Stream using the
peek
operation.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
peek
operation 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 thepeek
andforEach
methods 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
Arrays
into a single continuous Stream using theflatMap
method.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 anArray
object. However, using theflatMap
operation, 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
forEach
operation, as shown in the previous examples. -
You can retrieve the largest and smallest value of a Stream using the
max
andmin
final operations. And it is also possible to retrieve the number of elements of a Stream using thecount
final 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
max
andmin
operations, it is necessary to pass as an argument which comparator will be used. Since numbers have a natural order, that is, they implement theComparable
interface, 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 ofComparator
as an argument.The
max
andmin
operations returnOptional
because if Stream is empty it will be emptyOptional
. Since Java 8, with the addition of theOptional
class, this has been preferred over returningnull
as it facilitates functional programming. Thecount
operation does not need anOptional
, because even with an empty Stream will return0
. -
You can get the first element of the Stream using the final
findFirst
operation, 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
findAny
operation may yield different results.Like the
max
andmin
operations presented earlier,findAny
andfindFirst
return an emptyOptional
if Stream is empty. -
You can verify that Stream elements meet some validation by using the
allMatch
,anyMatch
, andnoneMatch
final 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
forEach
was invoked. -
You can chain the final Stream operation using lambda expressions in the
Optional
class.src/org/j6toj8/streams/usingstreams/Streams_Optional.javalink:../../../src/org/j6toj8/streams/usingstreams/Streams_Optional.java[role=include]
Note that the
ifPresent
method is from theOptional
class, even though in the second example, it might look like it is part of Stream. In other words, the final operation ismax
, andifPresent
is a called inOptional
and 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
peek
operation is before thelimit
operation, it is not performed for allStream
elements, 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
,IntStream
andLongStream
.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
generate
method.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
limit
operation, otherwise program execution would also be endless. -
You can use the
rangeClosed
operation 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
summaryStatistics
operation.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 output336
In 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 output336
In 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 output336
In 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
Collector
that 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
Collector
to 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
Collector
to 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
Collector
to 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
toMap
method 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
Collector
that 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
Collector
that partitions values inTrue
orFalse
from 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 singleString
separated by commas. -
You can add one more layer of transformation using a
Collector
using themapping
method.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.