The Java Stream API was introduced in Java 8, and it facilitates iteration over collections.
What is the Java Stream API
The Stream API provides you with a very useful ability to stream (aka iterate) over a Collection of elements while simultaneously processing these elements.
You can think of it the same way you would iterate over a Collection using a forEach loop. The Stream API simply implements the iteration process for us. In addition, while streaming (iterating), you can process the Collection of elements in any number of ways. The Stream API is built with extensive support for lambda expressions and method references.
Benefits of the Stream API
The stream API is a more efficient way of writing your code. Here's an example of summing a List of integers using a for loop.
int sum = 0;
List<Integer> nums = Arrays.asList(15, 98, 56);
List<Double> squareRoots = new ArrayList<>();
for(Integer number : nums) {
double squareRoot = Math.sqrt(number);
squareRoots.add(squareRoot);
}
System.out.println(squareRoots);
Now, using the stream API would transform that into this:
List<Integer> nums = Arrays.asList(15, 98, 56);
List<Double> squareRoots = nums.stream().map(Math::sqrt)
System.out.println(squareRoots);
As you can see, it is much more efficient! There are also some new things in this code, such as the map operation.
What is Java Stream Map
According to the Oracle docs, the stream API "supports functional-style operations on streams of elements, such as map-reduce transformations on collections."
When working with the stream API, there are a number of list operations that are commonly used. The map() operation, for example, will transform each element of the Collection by applying a function.
In fact, there are two types of operations that can be applied to a stream.
Non-Terminal Operations
Non-terminal operations are operations that do not terminate the stream. They often modify or filter the data in the stream. Some examples of non-terminal operations include:
map()filter()sorted()distinct()limit()skip()
Terminal Operations
Terminal operations will terminate the stream. They often return a specific result such as sum() or count(). Some examples of terminal operations include:
forEach()toArray()sum()count()min()/max()collect()anyMatch()/allMatch()
How To Use a Stream
As mentioned, a stream gives you the ability to iterate over a collection while simultaneously processing that collection. The manner in which you process elements within that Stream is with non-terminal and terminal operations (as described above).
Within each operation, you typically employ a lambda expression or method reference to inform the operation how it should map, filter, or reduce the data at that point in the stream.
To create a stream there is an order of steps that can be applied.
Steps to Create a Stream
Below are the three steps that can be applied when creating a stream.
- Obtain a stream
- Call zero or more non-terminal operations on the stream. Non-terminal operations add processing steps to the stream but do not actually start the iteration.
- Call at least one terminal operation on the stream. Terminal operations start the iteration of the stream and processing of each element.
How to Create Java Streams
As an example, suppose you have a List of integers. This list contains positive numbers and negative numbers. Now, your goal is to determine the sum of all positive integers in this list. To accomplish this using a stream:
- Acquire the list of numbers & create a stream object
stream = Arrays.stream(nums);
- Filter out all negative numbers (non-terminal operation):
.filter(x -> x > 0)
- Determine the sum of all remaining numbers (terminal operation)
.reduce(0, (Integer a, Integer b) -> a + b);
Here's the full code for the example.
Integer[] nums = {45, -87, -12, 77, -23};
Stream<Integer> stream = Arrays.stream(nums);
int sum = stream
.filter(x -> x > 0) // Lambda expression to filter out values < 1
.reduce(0, (Integer a, Integer b) -> a + b); // Reduce remaining nums into sum
System.out.println("The sum is: " + sum)
// OUTPUT: The sum is: 122
Notice that in the reduce() function above, you are passing in the following arguments, 0 and (Integer a, Integer b) -> a + b.
The first argument, 0, indicates that you want to start counting your sum from zero. The second argument is a lambda expression that adds two ints. The argument a will be 0 (the same zero from before), and b will be the first int in the list.
On subsequent iterations, the argument a will be the current sum, and "b" will be the next number in the list.
A Stream can have zero or more non-terminal operations, but it must contain one and only one terminal operation. Here's a quick breakdown of the non-terminal & terminal operations available to you. You will become more comfortable with each as you practice.
Java Stream Examples
Take a look at a number of examples so you can better understand the many uses of the stream API. These examples were found here. Each of these examples is discussed in the video on the next page of this course.
Print all numbers between 1 & 10:
/*
1. Create an IntStream
2. Iterate over the Integers 1 through 10
3. Use a method reference to print each number
*/
IntStream
.range(1, 10)
.forEach(System.out::print);
System.out.println();
//OUTPUT: 123456789
Print all numbers between 1 & 10, but skip 5:
/*
1. Create an IntStream
2. Iterate over the Integers 1 through 10, skip 5
3. Use a Lambda Expression to print each number
*/
IntStream
.range(1, 10)
.skip(5)
.forEach(x -> System.out.println(x));
//OUTPUT: 12346789
Determine the sum of a range of Integers:
/*
1. Declare int variable sum
2. Create an IntStream with range 1-5
3. Determine the sum
*/
int sum = IntStream.range(1, 5).sum();
System.out.println(sum);
// OUTPUT: 10
Stream a list of names, sort them, get the first name in the sorted list, and print it:
//instructions in-line below
Stream.of("Ava", "Aneri", "Alberto") //Create a Stream from a list of names
.sorted() // Sort the elements in the list
.findFirst() // Get the first element in the sorted list
.ifPresent(System.out::println); // Print the first element, if it exists
// OUTPUT: Alberto
Stream, Sort, Filter, and List a Stream:
String[] names = {"Al", "Ankit", "Kushal", "Brent", "Sarika",
"Amanda", "Hans", "Shivika", "Sarah"};
//same as Stream.of(names)
Arrays.stream(names)
// Lambda exp to filter out names that don't start with "S"
.filter(x -> x.startsWith("S"))
// Sort the remaining names
.sorted()
// Print each remaining name
.forEach(System.out::println);
/* OUTPUT:
Sarah
Sarika
Shivika
*/
Create a Stream Object:
// Create a Stream object of type String
Stream stream = Arrays.stream(names);
// Iterate over the Stream & print with Lambda
stream.forEach((String element) -> System.out.print(element));
// OUTPUT: Al Ankit Kushal Brent Sarika Amanda Hans Shivika Sarah
Use the map() function to average the squares of an int array:
// Create a Stream from an int array
Arrays.stream(new int[] {2, 4, 6, 8, 10})
// Use a Lambda to "square" each int in the array
.map(x -> x * x)
// Determine the average of all (squared) numbers
.average()
// Use a method reference to print the avg
.ifPresent(System.out::println);
// OUTPUT: 44.0
Create a Stream from a List, use the map() function to modify each String, filter and print:
// Create a list of people
List<String> people = Arrays.asList("Al", "Ankit", "Brent", "Sarika", "Amanda", "Hans", "Shivika", "Sarah");
// Create the Stream
people.stream()
// Map each element to lowercase
.map(String::toLowerCase)
// Filter out all names that do not start with "a"
.filter(x -> x.startsWith("a"))
// Print out all remaining elements
.forEach(System.out::println);
/* OUTPUT:
al
ankit
amanda
*/
Stream data from a text file, sort, filter, and print:
StringbandsFile = "src/resources/bands.txt";
Stream<String> bands = Files.lines(Paths.get(bandsFile));
bands
// Sort the records in the file
.sorted()
// Filter out all band names < 13 chars in length
.filter(x -> x.length() > 13)
// Print each remaining element
.forEach(System.out::println);
// Close the connection to the file
bands.close();
/* OUTPUT:
Jackson Browne
Mumford and Sons
Rolling Stones
*/
Determine which bands contain the characters "jit":
// Create a list from a text file
List<String> bands2 = Files.lines(Paths.get(bandsFile))
// Filter out elements that don't contain "jit"
.filter(x -> x.contains("jit"))
// Put all the remaining elements in the bands2 list
.collect(Collectors.toList());
// Stream list, print each element
bands2.forEach(x -> System.out.println(x));
// OUTPUT: Arijit Singh
Stream and parse data from CSV file:
Stream<String> rows2 = Files.lines(Paths.get(dataFile));
rows2
// Call String.split() on comma - returns array of values
.map(x -> x.split(","))
// Filter out all resulting arrays without length of 3
.filter(x -> x.length == 3)
// Filter out short band names
.filter(x -> Integer.parseInt(x[1]) > 15)
// Print
.forEach(x -> System.out.println(x[0] + " " + x[1] + " " + x[2]));
//close file connection
rows2.close();
Stream data from the CSV file into a HashMap:
//get Stream from file
Stream<String> rows3 =
Files.lines(Paths.get(dataFile));
//declare empty HashMap
Map<String, Integer> map = new HashMap<>();
map = rows3
// Split() the CSV row (a String) into an array of Strings
.map(x -> x.split(","))
// Filter out resulting arrays with length < 3
.filter(x -> x.length == 3)
// Filter out short band names
.filter(x -> Integer.parseInt(x[1]) > 13)
// Collect data into a Map data structure
.collect(Collectors.toMap(
// The first argument is the key
x -> x[0],
// The second arg is the value
x -> Integer.parseInt(x[1])));
// Close the file connection
rows3.close();
for (String key : map.keySet()) {
// Print each key in the HashMap
System.out.println(key + " " + map.get(key));
}
Determine the sum of a list of Doubles:
double total = Stream.of(7.3, 1.5, 4.8)
.reduce(0.0, (Double a, Double b) -> a + b);
System.out.println("Total = " + total);
// OUTPUT: 13.600000000000001
Summary: What is a Java Stream
- The
streamAPI helps to iterate over aCollection - Streams reduce the length of your code and increase efficiency
- Streams are compatible with functional interfaces and lambda expressions
- Streams can contain terminal and non-terminal operations
- Streams must contain one and only one terminal operation
- Streams can contain zero or more non-terminal operations
Steps to Create Java Streams
- Obtain a stream
- Call zero or more non-terminal operations on the stream
- Call at least one terminal operation on the stream
Commonly Used Non-Terminal Operations
map()filter()sorted()distinct()limit()skip()
Commonly Used Terminal Operations
forEach()toArray()sum()count()min()/max()collect()anyMatch()/allMatch()