Java 8 Stream
Java 8 API introduces a new abstraction called Stream, which allows you to process data in a declarative way.
Stream provides a high-level abstraction for operating on Java collections and expressing them in a manner similar to SQL statements querying data from a database.
The Stream API can significantly enhance Java programmers' productivity, enabling them to write efficient, clean, and concise code.
This approach treats the collection of elements to be processed as a stream that flows through a pipeline, where it can be processed at various nodes such as filtering, sorting, and aggregation.
The element stream passes through intermediate operations in the pipeline and is finally processed by terminal operations to produce the results of the preceding operations.
+--------------------+ +------+ +------+ +---+ +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+ +------+ +------+ +---+ +-------+
The above process translates to the following Java code:
List<Integer> transactionsIds =
widgets.stream()
.filter(b -> b.getColor() == RED)
.sorted((x,y) -> x.getWeight() - y.getWeight())
.mapToInt(Widget::getWeight)
.sum();
What is a Stream?
A Stream is a sequence of elements from a data source that supports aggregate operations.
Elements are objects of a specific type, forming a queue. Java Streams do not store elements; they are computed on demand.
Data Source The source of the stream. It can be a collection, array, I/O channel, or generator, among others.
Aggregate Operations Operations similar to SQL statements, such as filter, map, reduce, find, match, sorted, etc.
Unlike previous Collection operations, Stream operations have two fundamental characteristics:
Pipelining Intermediate operations return the stream itself, allowing multiple operations to be chained into a pipeline, akin to a fluent style. This enables optimization of operations, such as lazy evaluation and short-circuiting.
Internal Iteration Previously, iterating over a collection was done using an Iterator or a For-Each loop, explicitly iterating outside the collection, known as external iteration. Streams provide an internal iteration method, implemented via the Visitor pattern.
Generating Streams
In Java 8, the Collection interface has two methods to generate streams:
stream() Creates a sequential stream for the collection.
parallelStream() Creates a parallel stream for the collection.
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
forEach
The Stream interface introduces a new method 'forEach' to iterate over each data in the stream. The following code snippet uses forEach to output 10 random numbers:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
map
The map method is used to map each element to its corresponding result. The following code snippet uses map to output the square of each element:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// Get the square of each element
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
filter
The filter
method is used to filter out elements based on specified conditions. The following code snippet uses the filter
method to filter out empty strings:
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// Get the count of empty strings
long count = strings.stream().filter(string -> string.isEmpty()).count();
limit
The limit
method is used to obtain a specified number of elements from the stream. The following code snippet uses the limit
method to print out 10 data items:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
sorted
The sorted
method is used to sort the stream. The following code snippet uses the sorted
method to sort 10 random numbers:
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
Parallel (parallel) processing
parallelStream
is an alternative method for parallel processing of streams. The following example uses parallelStream
to output the count of empty strings:
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// Get the count of empty strings
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();
We can easily switch between sequential and parallel processing.
Collectors
The Collectors
class implements many reduction operations, such as converting streams into collections and aggregating elements. Collectors
can be used to return lists or strings:
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("Filtered List: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("Merged String: " + mergedString);
Statistics
Additionally, collectors that produce statistical results are very useful. They are mainly used on basic types like int, double, long, and can be used to produce statistics like the following:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Highest number in List : " + stats.getMax());
System.out.println("Lowest number in List : " + stats.getMin());
System.out.println("Sum of all numbers : " + stats.getSum());
System.out.println("Average of all numbers : " + stats.getAverage());
Complete Stream Example
Place the following code into the Java8Tester.java
file:
Java8Tester.java File
import java.util.ArrayList;
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.Map;
public class Java8Tester {
public static void main(String args[]){ System.out.println("Using Java 7: ");
// Count empty strings
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
System.out.println("List: " +strings);
long count = getCountEmptyStringUsingJava7(strings);
System.out.println("Number of empty strings: " + count);
count = getCountLength3UsingJava7(strings);
System.out.println("Number of strings with length 3: " + count);
// Remove empty strings
List<String> filtered = deleteEmptyStringsUsingJava7(strings);
System.out.println("Filtered list: " + filtered);
// Remove empty strings and join them with a comma
String mergedString = getMergedStringUsingJava7(strings,", ");
System.out.println("Merged string: " + mergedString);
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// Get the square of each number
List<Integer> squaresList = getSquares(numbers);
System.out.println("List of squares: " + squaresList);
List<Integer> integers = Arrays.asList(1,2,13,4,15,6,17,8,19);
System.out.println("List: " +integers);
System.out.println("Largest number in the list: " + getMax(integers));
System.out.println("Smallest number in the list: " + getMin(integers));
System.out.println("Sum of all numbers: " + getSum(integers));
System.out.println("Average: " + getAverage(integers));
System.out.println("Random numbers: ");
// Output 10 random numbers
Random random = new Random();
for(int i=0; i < 10; i++){
System.out.println(random.nextInt());
}
System.out.println("Using Java 8: ");
System.out.println("List: " +strings);
count = strings.stream().filter(string->string.isEmpty()).count();
System.out.println("Number of empty strings: " + count);
count = strings.stream().filter(string -> string.length() == 3).count();
System.out.println("Number of strings with length 3: " + count);
filtered = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.toList());
System.out.println("Filtered list: " + filtered);
mergedString = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("Merged string: " + mergedString);
squaresList = numbers.stream().map(i ->i*i).distinct().collect(Collectors.toList());
System.out.println("List of squares: " + squaresList);
IntSummaryStatistics stats = integers.stream().mapToInt((x) ->x).summaryStatistics();
System.out.println("Largest number in the list: " + stats.getMax());
System.out.println("Smallest number in the list: " + stats.getMin());
System.out.println("Sum of all numbers: " + stats.getSum());
System.out.println("Average: " + stats.getAverage());
System.out.println("Random numbers: ");
random.ints().limit(10).forEach(System.out::println);
}
mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("Merged String: " + mergedString);
squaresList = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList());
System.out.println("Squares List: " + squaresList);
System.out.println("List: " + integers);
IntSummaryStatistics stats = integers.stream().mapToInt(x -> x).summaryStatistics();
System.out.println("Maximum number in List : " + stats.getMax());
System.out.println("Minimum number in List : " + stats.getMin());
System.out.println("Sum of all numbers : " + stats.getSum());
System.out.println("Average of all numbers : " + stats.getAverage());
System.out.println("Random Numbers: ");
random.ints().limit(10).sorted().forEach(System.out::println);
// Parallel processing
count = strings.parallelStream().filter(string -> string.isEmpty()).count();
System.out.println("Count of empty strings: " + count);
}
private static int getCountEmptyStringUsingJava7(List<String> strings) {
int count = 0;
for (String string : strings) {
if (string.isEmpty()) {
count++;
}
}
return count;
}
private static int getCountLength3UsingJava7(List<String> strings) {
int count = 0;
for (String string : strings) {
if (string.length() == 3) {
count++;
}
}
return count;
}
private static List<String> deleteEmptyStringsUsingJava7(List<String> strings) {
List<String> filteredList = new ArrayList<String>();
for (String string : strings) {
if (!string.isEmpty()) {
filteredList.add(string);
}
}
return filteredList;
}
private static String getMergedStringUsingJava7(List<String> strings, String separator) {
StringBuilder stringBuilder = new StringBuilder();
for (String string : strings) {
if (!string.isEmpty()) {
public String mergeStrings(List<String> strings, String separator) {
StringBuilder stringBuilder = new StringBuilder();
for (String string : strings) {
if (string != null && !string.isEmpty()) {
stringBuilder.append(string);
stringBuilder.append(separator);
}
}
String mergedString = stringBuilder.toString();
return mergedString.substring(0, mergedString.length() - 2);
}
private static List<Integer> getSquares(List<Integer> numbers) {
List<Integer> squaresList = new ArrayList<>();
for (Integer number : numbers) {
Integer square = number * number;
if (!squaresList.contains(square)) {
squaresList.add(square);
}
}
return squaresList;
}
private static int getMax(List<Integer> numbers) {
int max = numbers.get(0);
for (int i = 1; i < numbers.size(); i++) {
Integer number = numbers.get(i);
if (number > max) {
max = number;
}
}
return max;
}
private static int getMin(List<Integer> numbers) {
int min = numbers.get(0);
for (int i = 1; i < numbers.size(); i++) {
Integer number = numbers.get(i);
if (number < min) {
min = number;
}
}
return min;
}
private static int getSum(List<Integer> numbers) {
int sum = numbers.get(0);
for (int i = 1; i < numbers.size(); i++) {
sum += numbers.get(i);
}
return sum;
}
private static int getAverage(List<Integer> numbers) {
return getSum(numbers) / numbers.size();
}
Executing the above script, the output is:
$ javac Java8Tester.java
$ java Java8Tester
Using Java 7:
List: [abc, , bc, efg, abcd, , jkl]
Number of empty strings: 2
Number of strings with length 3: 3
Filtered list: [abc, bc, efg, abcd, jkl]
Merged string: abc, bc, efg, abcd, jkl
List of squares: [9, 4, 49, 25]
List: [1, 2, 13, 4, 15, 6, 17, 8, 19]
Maximum number in the list: 19
Minimum number in the list: 1
Sum of all numbers: 85
Average: 9
Random numbers:
-393170844
-963842252
447036679
-1043163142
-881079698
221586850
-1101570113
576190039
-1045184578
1647841045
Using Java 8:
List: [abc, , bc, efg, abcd, , jkl]
Number of empty strings: 2
Number of strings with length 3: 3
Filtered list: [abc, bc, efg, abcd, jkl] Concatenated string: abc, bc, efg, abcd, jkl Squares list: [9, 4, 49, 25] List: [1, 2, 13, 4, 15, 6, 17, 8, 19] Largest number in the list: 19 Smallest number in the list: 1 Sum of all numbers: 85 Average: 9.444444444444445 Random numbers: -1743813696 -1301974944 -1299484995 -779981186 136544902 555792023 1243315896 1264920849 1472077135 1706423674 Number of empty strings: 2