Java 8 introduced a plethora of new features, but one of the most revolutionary is the Stream API. Designed to facilitate functional-style operations on streams of elements, the Stream API has become an essential tool for Java developers. In this post, we'll dive into the fundamentals of the Stream API, explore its capabilities, and provide practical examples to help you master this powerful feature.
What is a Stream?
In Java 8, a stream is a sequence of elements that supports various methods to perform computations upon those elements. These computations can be:
Intermediate operations that transform a stream into another stream (e.g., filter, map).
Terminal operations that produce a result or side-effect (e.g., forEach, reduce, collect).
Streams differ from collections as they are designed to support functional-style operations and can be processed in parallel.
Creating Streams
You can create streams from collections, arrays, or even generate them. Here are some common ways to create a stream:
From a Collection:
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
From an Array:
String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);
Using Stream.of:
Stream<String> stream = Stream.of("a", "b", "c");Generating Streams:
Stream<Double> randomNumbers = Stream.generate(Math::random).limit(10);
Stream<Integer> oddNumbers = Stream.iterate(1, n -> n + 2).limit(10);
Intermediate Operations
Intermediate operations transform a stream into another stream. They are lazy, meaning they are not executed until a terminal operation is invoked.
filter: Filters elements based on a condition.
List<String> result = list.stream()
.filter(s -> s.startsWith("a"))
.collect(Collectors.toList());
map: Transforms each element using a provided function.
List<Integer> lengths = list.stream()
.map(String::length)
.collect(Collectors.toList());
sorted: Sorts the stream elements.
List<String> sortedList = list.stream()
.sorted()
.collect(Collectors.toList());
distinct: Removes duplicate elements.
List<String> distinctList = list.stream()
.distinct()
.collect(Collectors.toList());
Terminal Operations
Terminal operations produce a result or side-effect and mark the end of the stream pipeline.
forEach: Applies a function to each element.
list.stream()
.forEach(System.out::println);
collect: Converts the stream into a collection or other data structure.
List<String> resultList = list.stream()
.filter(s -> s.startsWith("a"))
.collect(Collectors.toList());
reduce: Aggregates the elements using an associative accumulation function.
Optional<String> concatenated = list.stream()
.reduce((s1, s2) -> s1 + s2);
count: Returns the number of elements in the stream.
long count = list.stream()
.filter(s -> s.startsWith("a"))
.count();
findFirst: Finds the first element in the stream.
Optional<String> firstElement = list.stream()
.findFirst();
Parallel Streams
One of the most powerful features of the Stream API is the ability to process streams in parallel to leverage multi-core architectures.
List<String> parallelList = list.parallelStream()
.filter(s -> s.startsWith("a"))
.collect(Collectors.toList());
Using parallel streams can significantly improve performance, especially for large datasets. However, it's essential to ensure that the operations within the stream are thread-safe.
Practical Example
Let's see a practical example that combines various Stream API features. We'll process a list of persons to find the average age of those whose names start with a given letter.
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
List<Person> persons = Arrays.asList(
new Person("Alice", 30),
new Person("Bob", 20),
new Person("Charlie", 25),
new Person("David", 35)
);
double averageAge = persons.stream()
.filter(p -> p.getName().startsWith("A"))
.mapToInt(Person::getAge)
.average()
.orElse(0);
System.out.println("Average age: " + averageAge);
In this example, we filter the list of persons to include only those whose names start with "A", map their ages to an IntStream, and then compute the average age.
Conclusion
The Stream API in Java 8 provides a powerful and flexible way to process data. By mastering streams, you can write more concise, readable, and efficient code. Whether you're performing simple transformations or complex data manipulations, the Stream API is an invaluable tool in any Java developer's toolkit.
Happy coding!
Share this my publication, if you get some value from it.
Nice explanation