Java 8 Stream API
A stream is a sequence of objects that is obtained from a collection like list, set etc. The main purpose of streams is to do some operations in functional style using lambda expressions and get the desired results.
Features of Stream API
- Streams are not data structures themselves. Instead they are formed from the data from a collection, array or IO channels.
- A stream can be understood as a view of a collection. Therefore, whatever operations we do on streams doesn’t change the underlying collection. It only give the results according to the pipelined methods of stream api.
- There are intermediate and terminal operations. Intermediate operation is executed in a lazy way, and it returns a stream as a result. For this reason, various intermediate operations can be pipelined. Whereas, terminal operations mark the end of a stream and return the result.
Types of Stream Operations
Before understanding streams further, we need to understand the types of operations that can be done on streams. There are two types of stream operations: Intermediate and Terminal Operations
Intermediate Operations
Intermediate operations on a stream, as its name suggests, does some processing on the stream and returns the processed stream. The return of an intermediate operation is the stream itself, so that the next intermediate operation can work on it. Below are some examples of intermediate operations
- map() method :
The map() method is used to return a stream consisting of the results of applying the given function to the elements of this stream.
List<Integer> number = new ArrayList<>() {1, 2, 3, 4};
List square = number.stream().map(n -> n*n).collect(Collectors.toList());
2. filter() method :
The filter method is used to select elements as per the Predicate passed as argument.
List names = Arrays.asList("Rahul","Virat","Rohit");
List result = names.stream().filter(n -> n.startsWith("V")).collect(Collectors.toList());
Terminal Operations
Terminal operations are used for giving the final output for a Stream under operation. In the process, they terminate a Stream. Terminal Operations do not return a Stream as their output. Instead they can return any value other than Stream itself, or even no value (void), such as in the case of forEach() method. Examples of terminal methods are findAny(), allMatch(), forEach() etc.
collect() : The collect() method collects the result of the intermediate operations performed on the stream and returns it.
List<Integer> numbers = new ArrayList<>(){4, 7, 1, 2};
Set square = numbers.stream().map( n -> n*n ).collect(Collectors.toSet());
forEach() : The forEach() method is used to iterate through every element of the stream.
List<Integer> numbers = new ArrayList<>(){4, 7, 1, 2};
numbers.stream().map(x->x*x).forEach(y->System.out.println(y));
Ways to create java8 streams
- Empty Stream
Stream<String> emptyStream = Stream.empty();
Empty streams are used to avoid returning null for streams with no element.
public Stream<String> streamOf(List<String> list) {
return list == null || list.isEmpty() ? Stream.empty() : list.stream();
}
2. Stream of Collection
We can create a stream of any type of collection like List, Set, Collection.
Collection<String> collection = Arrays.asList("Arjun", "Shankar", "Javed");
Stream<String> streamOfCollection = collection.stream();
3. Stream of Array
We can also create a stream of an Array
Stream<String> streamOfArray = Stream.of("X", "Y", "Z");
String[] arr = new String[]{"X", "Y", "Z"};
Stream<String> arrStream = Arrays.stream(arr);
//below code will create stream staring from index 1(inclusive) to 3(exclusive)
//thus Y, Z in this case
Stream<String> partialArrStream = Arrays.stream(arr, 1, 3);
4. Stream.iterate() method
Another way of creating streams is by using iterate() method.
Stream<Integer> streamIterated = Stream.iterate(10, n -> n + 3).limit(20);
In the above example, the first element of the resulting stream is the first argument of the iterate() method i.e. 10. Every following element will be generated using the specified function on the previous element. In the above example, the second element will be 13, third element will be 16, fourth 19 and so on. And there would be a total of 20 element. Thus the last element would be 67 in this case.
Code example to understand stream api
package com.javatrainingschool;
public class Book {
private String name;
private int price;
//getters and setters methods
//constructors
//toString method
}
package com.javatrainingschool;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
public static void main(String[] args) {
Book b1 = new Book("Geetanjali", 200);
Book b2 = new Book("Godan", 150);
Book b3 = new Book("The God of Small Things", 170);
List<Book> books = new ArrayList<>();
books.add(b1);
books.add(b2);
books.add(b3);
//get a stream of Books
Stream<Book> bookStream = books.stream();
//filter the records and form another list where book price is greater than 150
List<Integer> bookPriceList = bookStream.filter(p -> p.getPrice() > 150).map(p -> p.getPrice()).collect(Collectors.toList());
bookPriceList.forEach(p -> System.out.println(p));
}
}
Output :
200
170
Terminal method reduce() example
reduce() method is a terminal operation. Let’s see one example to understand its functioning. Reducing is a terminal operation that aggregates a stream into primitive or a object type. Stream api contains many reduction operations such as agerage(), sum(), count(), min(), max() etc.
Book.java
package com.javatrainingschool;
public class Book {
private int id;
private String name;
private int price;
//getters and setters methods
//constructors
//toString method
}
ReduceMethodExample.java
package com.javatrainingschool;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class ReduceMethodExample {
public static void main(String[] args) {
List<Book> books = new ArrayList<>();
Book b1 = new Book(1, "The Kite Runner", 400);
Book b2 = new Book(2, "Java - The complete Reference", 500);
Book b3 = new Book(3, "Hibernte", 200);
Book b4 = new Book(4, "Learn Unix in 21 Days", 300);
books.add(b1);
books.add(b2);
books.add(b3);
books.add(b4);
Stream<Book> stream = books.stream();
int totalPriceOfAllBooks = stream.map(b -> b.getPrice()).reduce(0, (totalPrice, oneBookPrice) -> totalPrice + oneBookPrice);
System.out.println("Total Price of all the books = " + totalPriceOfAllBooks);
}
}
Output :
Total Price of all the books = 1400
Some important stream api methods
1. forEach() method of Stream api
Stream has provided a new method forEach() to iterate each element of the stream. The below example shows how to print 5 random numbers using forEach().
Random random = new Random();
random.ints().limit(5).forEach(System.out::println);
2. filter() method of Stream api
filter() method is used to remove elements based on some criteria. The below example prints a count of empty strings using filter.
List<String>strings = Arrays.asList("Ram", "Mohan", "", "Krishna", "","", "Keshav");
//count of empty string
int count = strings.stream().filter(string -> string.isEmpty()).count();
3. sorted() method of Stream api
As the name suggests, this metohd is used to sort the stream
Random random = new Random();
random.ints().limit(100).sorted().forEach(System.out::println);
4. limit() method of Stream api
limit() method is used to limit the records of stream
Random random = new Random();
random.ints().limit(100).forEach(System.out::println);
Parallel Stream
Till now, whatever we have read about streams are sequential streams. There is another kind of stream that java8 and higher versions provide and that is parallel streams. They are used for parallel processing.
Parallel streams make use of multiple cores of the processor. With normal stream processing is sequential, whereas with parallel streams, we can divide the code into multiple streams that execute in parallel on separate cores. Final result is the combination of the individual results. However, the order of execution cannot be controlled.
Ways to create a parallel Stream
- By calling parallel() method on a normal (sequential) stream.
package com.javatrainingschool;
import java.util.Arrays;
import java.util.List;
public class ParallelStreamExample {
public static void main(String[] args) {
List<Integer> oddNumbers = Arrays.asList(1, 3, 5, 7, 9);
oddNumbers.stream().parallel().forEach(System.out::println);
}
}
- Using parallelStream() method on a collection.
package com.javatrainingschool;
import java.util.Arrays;
import java.util.List;
public class ParallelStreamExample {
public static void main(String[] args) {
List<Integer> oddNumbers = Arrays.asList(1, 3, 5, 7, 9);
oddNumbers.parallelStream().forEach(System.out::println);
}
}
Output :
5
3
7
1
9
Observation : We can see here the output is not sequential. This is because of parallel stream. If it were done using normal stream, the same output would have been in sequential manner.
After so much of brainstorming with streams, tea is the need of the hour. Enjoy a cup of tea/coffee. Yes, you deserve one at this moment.