This tutorial will provide you an overview of Java 8 Stream API along with example code snippets. Stream is a new API introduced in Java 8 which supports various operations to efficiently process data. Let’s start with the definition of Stream.
What is Stream?
If you check the API doc of Java 8, Stream is nothing but a typed interface. It represents a sequence of objects which supports sequential and parallel aggregate operations. A thing to note that Stream does not hold any data, it only processes the data as per operations performed on it. It does not change the data under process.
Do not confuse Stream with any Collection API class. Collection API classes like ArrayList, HashMap holds data but Stream does not hold any data. You can create Stream from these collection classes but Stream will not replace these classes.
How to create Stream?
A stream can be obtained from collection classes, arrays, Stream.Builder etc. Let’s check all of them one by one:
Stream of Collection
The java.util.Collection interface has default method stream() which provides a stream of items in the collection. This also gets inherited to all its sub-interfaces like List, Set.
Collection<String> stringCollection = Arrays.asList("One", "Two", "Three"); Stream<String> streamOfCollection = stringCollection.stream();
Stream of values
The Stream interface has a static method of(T… values) which returns stream of all elements given in the argument.
Stream<Integer> streamOfIntegers = Stream.of(1, 2, 3);
Stream of Array
The java.util.Arrays class provides methods to create a stream from an array. The source can be an array of objects or array of primitives. In case of primitives, separate stream interfaces specific to that primitive type is also available. For example, IntStream, LongStream, and DoubleStream.
Person[] personArray = {new Person("bytes", 18), new Person("tree", 20)}; Stream<Person> personStream = Arrays.stream(personArray); int[] intArray = {1, 2, 3}; IntStream intStream = Arrays.stream(intArray);
Stream from Stream.Builder
In case you want to add an individual element in the stream, Stream provides a builder method. This will return an instance of Stream.Builder which allows adding each element individually.
Stream.Builder<String> builder = Stream.builder(); Stream<String> streamFromBuilder = builder.add("One").add("Two").add("Three").build();
Infinite Stream
We can create an infinite stream by keep on applying a particular operation/function to the result of previous operation/function. For example, if you want a stream containing elements such that each element is greater than 2 from the previous element. If we start with 0 as the first element then stream should be 0, 2, 4, 6, 8 and so on.
Stream provides Stream<T> iterate(T seed, UnaryOperator<T> f) method for creating such stream. But remember that you have to limit on how many such elements you want in the stream, otherwise it will eat up the memory.
Stream<Integer> streamFromIterate = Stream.iterate(0, n -> n + 2).limit(10);
Operations in Stream
After getting a stream from its source, we can start performing various operations on it. Stream operations are broadly categorized into two types: Intermediate Operations and Terminal Operations.
Intermediate Operations
These operations are also known as “lazy” operations. They are “lazy” because the actual processing will not happen unless the terminal operation is present. The easiest way to identify intermediate operations is to check the return type of the operation. These operations return a stream after completing the process. For example, map() and filter() methods in Stream.
Terminal Operations
As name suggests, these are final operations performed on a Stream. Terminal operations do not have return type as Stream like intermediate operations. They either return a value or nothing at all. For example, count() and forEach() methods.
A thing to note here that intermediate operations will execute only if a terminal operation is present in the chain of operations.
For example, In below code only intermediate operations are performed on a stream, as a result, nothing is printed.
Stream.of("one", "two", "Three").map(s -> s.toUpperCase()) .peek(System.out::println);
If you add a count() (or any terminal operation like collect) at the end, it will print elements of string in upper case by executing preceding intermediate operations.
Stream.of("one", "two", "Three").map(s -> s.toUpperCase()) .peek(System.out::println).count();
Remember that order of operations on stream should be:
- Source of Stream
- Zero/more intermediate operations
- Terminal operation
Stream Examples
Enough with theory now. Let’s see some practical use of Stream API.
Convert List of objects to another List of objects
Below example creates List of String from List of Person object by using name property of Person.
List<Person> persons = new ArrayList<>(); persons.add(new Person("Bytes", 18)); persons.add(new Person("Tree", 20)); persons.add(new Person("Streams", 22)); List<String> names = persons.stream().map(p -> p.getName()).collect(Collectors.toList()); System.out.println(names);
Output:
[Bytes, Tree, Streams]
Create new list by filtering elements in List
Below example filters even numbers from List of integers and put them in a new List.
//create list of integers from 1 to 10 List<Integer> numbers = Stream.iterate(1, n -> n + 1).limit(10).collect(Collectors.toList()); List<Integer> evenNumbers = numbers.stream().filter(n -> (n.intValue() % 2 == 0)) .collect(Collectors.toList()); System.out.println(evenNumbers);
Output:
[2, 4, 6, 8, 10]
Find element from a List by property of element
List<Person> persons = new ArrayList<>(); persons.add(new Person("Bytes", 18)); persons.add(new Person("Tree", 20)); persons.add(new Person("Streams", 22)); Optional<Person> result = persons.stream().filter(p -> p.getName().equals("Bytes")).findAny(); if (result.isPresent()) { System.out.println(result.get()); } else { System.out.println("Not Found"); }
Output:
Person:: Name: Bytes, Age: 18
Find maximum and minimum number from List of Integers
List<Integer> integers = Arrays.asList(3, 5, 45, 52, 5, 14); Optional<Integer> max = integers.stream().max(Comparator.naturalOrder()); Optional<Integer> min = integers.stream().min(Comparator.naturalOrder()); System.out.println("Max: " + max.get()); System.out.println("Min: " + min.get());
Output:
Max: 52 Min: 3
Get sum of integers in using reduce operation
int[] intArray = {23,45,76,12,67,89}; Arrays.stream(intArray).reduce(Integer::sum) .ifPresent(s -> System.out.println("Sum: " + s));
Output:
Sum: 312
Source Code
Complete source code of examples given in this tutorial is available on Github
Hope this helps you to understand the basic of Stream API. For any queries, suggestions please leave your message in comments section.