Java Streams: Declarative Data Processing in Java 8 and Beyond
Explore Java Streams, introduced in Java 8, which allow you to process data in a declarative style similar to SQL queries. Learn how streams simplify data handling and enable efficient, functional programming in Java.
Java - Streams
Stream is a new abstract layer introduced in Java 8. With streams, you can process data in a declarative way, similar to SQL statements. For instance, consider the following SQL statement:
SQL Example
SELECT max(salary), employee_id, employee_name FROM Employee
The SQL expression above automatically retrieves the details of the employee with the highest salary, without requiring the developer to perform any calculations. In contrast, when using the Java collections framework, developers typically need to write loops and perform repetitive checks, which can be inefficient, especially with the availability of multi-core processors. Writing parallel processing code can be error-prone.
To address these challenges, Java 8 introduced the concept of streams, enabling developers to process data declaratively while efficiently utilizing multi-core architectures without writing specialized code.
What is a Stream in Java?
A stream represents a sequence of objects from a source and supports aggregate operations. The characteristics of a stream include:
- Sequence of Elements: A stream provides a set of elements of a specific type in a sequential manner, computing elements on demand. It does not store elements.
- Source: Streams can take collections, arrays, or I/O resources as input sources.
- Aggregate Operations: Streams support operations like filter, map, limit, reduce, find, and match.
- Pipelining: Most stream operations return a stream itself, allowing their results to be pipelined. Intermediate operations take input, process it, and return output to the target, while the collect() method serves as a terminal operation marking the end of the stream.
- Automatic Iterations: Stream operations handle iterations internally over source elements, unlike collections, which require explicit iteration.
Generating Streams in Java
With Java 8, the Collection interface provides two methods to generate a stream:
Syntax for Stream Generation
stream() - Returns a sequential stream from the collection.
parallelStream() - Returns a parallel stream from the collection.
For example:
Filtering Non-Empty Strings
List strings = Arrays.asList("xyz", "", "abc", "def", "ghi", "", "jkl");
List filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
forEach Method
The forEach
method allows you to iterate over each element in the stream. Below is an example of printing 10 random numbers using forEach
.
Printing Random Numbers
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
map Method
The map
method is used to transform each element to its corresponding result. The following code prints unique squares of numbers:
Unique Squares of Numbers
List numbers = Arrays.asList(4, 3, 3, 4, 9, 4, 5);
List squaresList = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList());
filter Method
The filter
method eliminates elements based on specified criteria. Here’s how to count empty strings:
Counting Empty Strings
List strings = Arrays.asList("xyz", "", "abc", "def", "ghi", "", "jkl");
int count = strings.stream().filter(string -> string.isEmpty()).count();
limit Method
The limit
method reduces the size of the stream. Here’s an example of printing 10 random numbers:
Limiting Random Numbers
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
sorted Method
The sorted
method sorts the stream. Below is an example of printing sorted random numbers:
Sorted Random Numbers
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
Parallel Processing
The parallelStream
method is an alternative to stream
for parallel processing. Here’s how to count empty strings using parallelStream
:
Counting Empty Strings with Parallel Stream
List strings = Arrays.asList("xyz", "", "abc", "def", "ghi", "", "jkl");
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();
Collectors
Collectors combine the results of processing stream elements. They can return a list or a string. For instance:
Using Collectors
List strings = Arrays.asList("xyz", "", "abc", "def", "ghi", "", "jkl");
List 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
Java 8 introduces statistics collectors to calculate various statistics during stream processing:
Calculating Statistics
List numbers = Arrays.asList(3, 5, 2, 8, 6, 4);
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());
Java Streams Example
Here's a complete example demonstrating the use of streams:
Complete Java Streams Example
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
public static void main(String[] args) {
List strings = Arrays.asList("xyz", "", "abc", "def", "ghi", "", "jkl");
// Filter non-empty strings
List filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("Filtered List: " + filtered);
// Count empty strings
long count = strings.stream().filter(string -> string.isEmpty()).count();
System.out.println("Count of Empty Strings: " + count);
}
}
Conclusion
Java Streams offer a powerful way to work with data in a functional style. They simplify complex operations and improve performance, making data processing in Java both efficient and easy to implement.