Streams in Java 8
One of the major feature of Java 8 is addition of Stream. It also has introduced the functional programming in Java. We will discuss different stream operations available in Collection, Array, IntStream with examples. We will also discuss the difference between Intermediate and Terminal operations.
Stream Operations
There are mainly three parts of any stream operations:-
1. Create Stream
These are called as Stream operation, which creates a stream from a given range or collection.
List<String>.stream()
, List<Object>.stream()
, Arrays.stream()
, IntStream.of()
, IntStream.range()
2. Process the Stream
These are called as Intermediate operation, which converts a stream to another stream as a result. They can be chained together to form a pipeline of Stream operations.
filter()
, map()
, flatMap()
, sorted()
, distinct()
, limit()
, skip()
3. Consume the Stream
These are called as Terminal operation, which converts a stream to result or collection or void. They can not be chained together. Any Stream operation pipeline must end with terminal operation.
forEach()
, collect()
, reduce()
, min()
, max()
, count()
, average()
, sum()
, anyMatch()
, allMatch()
, noneMatch()
, findFirst()
, findAny()
Intermediate vs Terminal Operations
1. Output: Output of intermediate operation is another stream whereas output of terminal operation is a collection, array or primitive.
2. Chaining: Stream operation pipeline can have as many as intermediate operators chained together but pipeline must end with terminal operator.
3. Lazy Evaluation: Intermediate operations are evaluated lazily whereas terminal operations are eager. The intermediate operations just remain as a pipeline, and executed only when the terminal operation is executed
4. Pipeline: Stream operations pipeline can have many intermediate operations but only one terminal operation.
List<String>.stream()
We will look at various stream operations pipelines. Let’s define a fruits
List collection first
List<String> fruits = Arrays.asList("mango", "apple", "banana", "grapes", "orange");
We now execute various examples of streams operations pipeline
Stream Operation | Intermediate Operation | ... | ... | Intermediate Operation | Terminal Operation
Example 1. Filter elements from List
stream() | filter() | collect()
Filter out all elements except grapes
from list fruits
List<String> result = fruits.stream() // convert list to stream
.filter(fruit -> !"grapes".equals(fruit)) // filter out grapes
.collect(Collectors.toList()); // collect the output and convert streams to a List
result.forEach(System.out::println);
Output
mango
apple
banana
orange
Example 2. Change all elements in List
stream() | map() | collect()
Map all elements of List fruits
to uppercase
List<String> result = fruits.stream() // convert list to stream
.map(fruit -> fruit.toUpperCase()) // map to uppercase
.collect(Collectors.toList()); // collect the output and convert streams to a List
result.forEach(System.out::println);
Output
MANGO
APPLE
BANANA
GRAPES
ORANGE
Example 3. Sort all elements in List
stream() | sorted() | collect()
Sort all elements of List fruits
in alphabetical order
List<String> result = fruits.stream() // convert list to stream
.sorted() // sort in alphabetical order
.collect(Collectors.toList()); // collect the output and convert streams to a List
result.forEach(System.out::println);
Output
apple
banana
grapes
mango
orange
Example 4. Multiple intermediate operations
stream() | filter() | map() | sorted() | collect()
Multiple processing of stream of List fruits
. Filter out all elements except grapes
, Map them to uppercase, Sort them alphabetically.
List<String> result = fruits.stream() // convert list to stream
.filter(fruit -> !"grapes".equals(fruit)) // filter out grapes
.map(fruit -> fruit.toUpperCase()) // map to uppercase
.sorted() // sort in alphabetical order
.collect(Collectors.toList()); // collect the output and convert streams to a List
result.forEach(System.out::println);
Output
APPLE
BANANA
MANGO
ORANGE
Example 5. Find elements in List
stream() | filter() | findAny() | orElse()
Filter mango
from the List fruits
, Return mango
if found or else return null
String fruit = fruits.stream() // convert list to stream
.filter(fruit -> "mango".equals(fruit)) // we love mango
.findAny() // If `findAny` then return found
.orElse(null); // If not found, return null
System.out.println(fruit);
Output
mango
List<Object>.stream()
Streams works with Objects Also.
Example 1. Find the 3 highest earning employees
Let’s first do it in our usual way:-
List<Employee> employees = getAllEmployees();
// Copy to new list to avoid mutating original array
List<Employee> copy = new ArrayList<>(employees);
// Sort descending
copy.sort((o1, o2) -> o2.getSalary() - o1.getSalary());
// Get first 3
for(int i=0; i<3; i++){
Employee employee = copy.get(i);
System.out.println(employee.getName());
}
Now use the streams to do the same:-
List<Employee> employees = getAllEmployees();
employees.stream()
.sorted(Comparator.comparingInt(Employee::getSalary).reversed())
.limit(3)
.map(Employee::getName)
.forEach(System.out::println)
Example 2. List Collectors (Terminal Operations)
Let’s look at various collectors (terminal operations) available:-
List<Employee> employees = getAllEmployees();
// to list
List<String> listOfEmps = employees.stream()
.limit(3)
.map(Employee::getName)
.collect(Collectors.toList());
// to set
Set<String> setOfEmps = employees.stream()
.limit(3)
.map(Employee::getName)
.collect(Collectors.toSet());
// to map
Map<String, Employee> mapOfEmps = employees.stream()
.limit(3)
.collect(Collectors.toMap(e -> e.name, e -> e));
// john, amy, marcy
String names = employees.stream()
.limit(3)
.map(Employee::getName)
.collect(Collectors.joining(","));
// group by dept
Map<String, List<Employee>> empByDept
= employees.stream()
.collect(Collectors.groupingBy(e -> e.dept));
// count employees in each dept
Map<String, Long> countByDept
= employees.stream()
.collect(Collectors.groupingBy(Employee::getDept, Collectors.counting()));
Example 3. Parallel Operations
Stream operations are executed sequentially by default. You can initiate parallel stream operations by using .parallel()
operation. It is recommended to user parallel operation only when List is considerably large otherwise there will be a performance hit.
// parallel stream
Map<String, List<Employee>> empMapByDept
= employees.stream()
.parallel()
.collect(Collectors.groupingBy(e -> e.dept));
IntStream.of() | .range()
Example 1. Run a for loop from 1 to n-1
int n = 6;
IntStream.range(1, n)
.forEach(System.out::println);
Output
1
2
3
4
5
Example 2. Get min, max, avg, count, and sum of given Array
Find the minimum of given number array:-
int[] nums = {4, 1, 13, 90, 16, 2, 0};
int min = IntStream.of(nums) //create stream
.min()
.getAsInt();
System.out.println("min: " + min);
Output
min: 0
getAsInt()
throw exception if min cannot be found e.g. if array is empty. Alternate method is ifPresent()
IntStream.of(new int[0])
.min()
.ifPresent(min -> System.out.println(min));
// OR
IntStream.of(new int[0])
.min()
.ifPresent(System.out::println);
We changed the lambda expression min -> System.out.println(min)
to double colon System.out::println
in later version. Both do the same thing.
Other similar statistics operations are:-
int[] nums = {4, 1, 13, 90, 16, 2, 0};
int min = IntStream.of(nums).min().getAsInt();
int max = IntStream.of(nums).max().getAsInt();
double avg = IntStream.of(nums).average().getAsDouble();
long count = IntStream.of(nums).count();
long sum = IntStream.of(nums).sum();
Alternatively you can create a stream only once do get all statistics:-
IntSummaryStatistics stats = IntStream.of(nums).summaryStatistics();
int min = stats.getMin();
int max = stats.getMax();
double avg = stats.getAverage();
long count = stats.getCount();
long sum = stats.getSum();
Example 3. Find 3 distinct smallest numbers from given Array
int[] nums = {4, 1, 13, 90, 16, 2, 0};
IntStream.of(nums)
.distinct()
.sorted()
.limit(3)
.forEach(System.out::println);
Output
0
1
2
Make note that streams works on the copy of the array and do not mutate the original array.
You can change the terminal (last) operation say if we want sum of 3 distinct small numbers:-
int[] nums = {4, 1, 13, 90, 16, 2, 0};
long sum = IntStream.of(nums)
.distinct()
.sorted()
.limit(3)
.sum();
Example 4. All possible operations on IntStream
Create a Stream
IntStream.of(numbers); // from Array
IntStream.range(1, 101); // 1..100
IntStream.rangeClosed(1, 100); // 1..100
IntStream.generate(supplier()); // from supplier
Process the Stream
IntStream.of(numbers).distinct(); // distinct
IntStream.of(numbers).sorted(); // sort
IntStream.of(numbers).limit(3); // get first 3
IntStream.of(numbers).skip(3); // skip first 3
IntStream.of(numbers).filter(n -> n%2==0); // only even
IntStream.of(numbers).map(n -> n*2); // double each num
IntStream.of(numbers).boxed(); // convert each num to Integer
Consume the Stream
IntStream.of(numbers).min(); // min
IntStream.of(numbers).max(); // max
IntStream.of(numbers).sum(); // sum
IntStream.of(numbers).average(); // average
IntStream.of(numbers).count(); // count
IntStream.range(1, 100).forEach(System.out::println); // print 1 to 99
IntStream.range(1, 100).toArray(); // collect into array
IntStream.range(1, 100).boxed().collect(Collectors.toList()); // collect into list
IntStream.of(numbers).anyMatch(n -> n%2==1); // is any num odd
IntStream.of(numbers).allMatch(n -> n%2==1); // are all num odd
Array.stream()
Similar to IntStream, we can create a stream from an array using Array.stream()
and apply similar operations
int[] intArray = new int[]{1, 3, 5, 7, 9, 3, 5, 99};
int min = Arrays.stream(nums).min().getAsInt();
int max = Arrays.stream(nums).max().getAsInt();
double avg = Arrays.stream(nums).average().getAsDouble();
long count = Arrays.stream(nums).count();
long sum = Arrays.stream(nums).sum();
Arrays.stream(intArray)
.distinct()
.forEach(System.out::println);