Java 8 provided a new additional package called java.util.stream. This package consists of classes, interfaces and enum to allows functional-style operations on the elements. You can use stream by importing java.util.stream package. We can use Java Stream API to implement internal iteration. Internal iteration provides several features such as sequential and parallel execution, filtering based on the given criteria, mapping etc. In this journal entry we will be understanding java 8 streams with examples.
Understanding Java 8 Streams
Let’s look at some of the core concepts of Java 8 Stream API and then we will walk through some more examples of most commonly used methods.
- Java Stream is a data structure that is computed on-demand.
- Java Stream doesn’t store data. It operates on the source data structure and produce pipelined data that can be used to perform specific operations.
- Most of the Java 8 Stream API method arguments are functional interfaces, so lambda expressions work very well with them.
- Internal iteration principle helps in achieving lazy-seeking in some of the stream operations. For example filtering, mapping, or duplicate removal can be implemented lazily, allowing higher performance and scope for optimization.
- Java 8 Stream support sequential as well as parallel processing, parallel processing can be very helpful in achieving high performance for large collections.
- Java 8 Streams are consumable, so there is no way to create a reference to stream for future usage and reuse the same stream multiple times.
Streams can be obtained in a number of ways. Some examples include:
- From a
Collection
via thestream()
andparallelStream()
methods; - From an array via
Arrays.stream(Object[])
; - From static factory methods on the stream classes, such as
Stream.of(Object[])
,IntStream.range(int, int)
orStream.iterate(Object, UnaryOperator)
; - The lines of a file can be obtained from
BufferedReader.lines()
; - Streams of file paths can be obtained from methods in
Files
; - Streams of random numbers can be obtained from
Random.ints()
; - Numerous other stream-bearing methods in the JDK, including
BitSet.stream()
,Pattern.splitAsStream(java.lang.CharSequence)
, andJarFile.stream()
.
java.util.Spliterator
Spliterator interface is used in Java 8 Stream to support parallel execution. Spliterator trySplit
method returns a new Spliterator that manages a subset of the elements of the original Spliterator.
Java 8 Streams operations and pipelines
Stream operations are divided into intermediate
and terminal
operations, and are combined to form stream pipelines
. A stream pipeline consists of a source (such as a Collection
, an array, a generator function, or an I/O channel); followed by zero or more intermediate operations such as Stream.filter
or Stream.map
; and a terminal operation such as Stream.forEach
or Stream.reduce
.
Intermediate Operations
- Returns a new List.
- They are always lazy; executing an intermediate operation such as
filter()
does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate. - Divided into Stateless and Stateful operations.
- Stateless operations, such as
filter
andmap
, retain no state from previously seen element when processing a new element — each element can be processed independently of operations on other elements. - Stateful operations, such as
distinct
andsorted
, may incorporate state from previously seen elements when processing new elements. - Stateful operations may need to process the entire input before producing a result.
- Stateless operations, such as
- Intermediate Operations are called Short Circuiting, if it may produce finite stream for an infinite stream.
limit()
andskip()
are two short circuiting operations.
Terminal Operations
- Operations, such as
Stream.forEach
orIntStream.sum
, may traverse the stream to produce a result or a side-effect. - Once the terminal operations is performed, the Stream pipeline is considered consumed and can no longer be used
- Almost all the cases of terminal operations are eager, completing their traversal of the data source and processing of the pipeline before returning.
- Terminal operations
iterator()
andspliterator()
are not eager; these are provided as an “escape hatch” to enable arbitrary client-controlled pipeline traversals in the event that the existing operations are not sufficient to the task. - Terminal operations are called as short circuiting, if it may terminate in finite time for an infinite stream. anyMatch(), allMatch(), noneMatch(), findFirst(), and findAny() are short circuiting terminal operations.
Java Stream Examples
We have tried to cover almost all the important parts of the Java 8 Stream API. Let’s see it in action with some java stream examples.
Java Streams Creation
Using Stream.of()
to create a stream for similar type of data.
Stream<Integer> intStream = Stream.of(1,2,3,4);
We can use Stream.of()
with array of objects also to create stream.
Integer intArray = new Integer(){1,2,3,4}; Stream<Integer> intStream = Stream.of(intArray);
We can use the collection stream()
and parallelStream()
to create sequential stream and parallel streams.
List<Integer> intList = new ArrayList<Integer>(); for (int k = 0; k < 1000; k++) { intList.add(k); } // Sequential Stream Stream<Integer> sStream = intList.stream(); // Parallel Stream Stream<Integer> pStream = intList.parallelStream();
We can use Stream.generate()
and Stream.iterate()
to create Streams
Stream<String> stream1 = Stream.generate(() -> {return "developersjournal";}); Stream<String> stream2 = Stream.iterate("developersjournal", (i) -> i);
We can use Arrays.stream()
and String.chars()
also to create streams.
LongStream is = Arrays.stream(new long[]{1,2,3,4}); IntStream is2 = "developersjournal".chars();
Java Streams conversion to Collections or Array
We can use java Stream collect()
method to get List, Map or Set from stream.
Stream<Integer> intStream = Stream.of(1,2,3,4); List<Integer> intList = intStream.collect(Collectors.toList()); System.out.println(intList); //prints [1, 2, 3, 4] //stream is closed, so we need to create it again intStream = Stream.of(1,2,3,4); Map<Integer,Integer> intMap = intStream.collect(Collectors.toMap(i -> i, i -> i+10)); System.out.println(intMap); //prints {1=11, 2=12, 3=13, 4=14}
toArray() method can be used to create an array from stream
Stream<Integer> intStream = Stream.of(1,2,3,4); Integer[] intArray = intStream.toArray(Integer[]::new); System.out.println(Arrays.toString(intArray)); //prints [1, 2, 3, 4]
In the next journal entry we will be looking at more Java 8 Stream examples.