Practical Java Streams Techniques: Filtering, Mapping, Reducing, and Grouping
This article introduces five practical Java Streams techniques—including precise filtering, map transformation, aggregation with reduce, and advanced grouping—to help developers write clearer, more efficient backend code, complete with code examples and explanations of each operation.
Java Streams, introduced in Java 8, revolutionized how developers handle collections by providing concise, readable, and maintainable data processing capabilities.
This article presents five useful techniques that simplify complex data transformations and make code clearer and more efficient.
Precise Filtering
Beginner
Imagine you have a list of products and want to filter out testers with an interesting "soul" attribute. The filter operation is ideal for this purpose.
public static void main(String[] args) {
List
testers = new ArrayList<>();
List
funTesters = testers.stream()
.filter(tester -> tester.getSoul().contains("Fun"))
.collect(Collectors.toList());
}The code creates a stream from the testers list, filters testers whose getSoul() contains "Fun", and collects the results into a new funTesters list.
Advanced
The power of filter lies in its ability to combine multiple conditions. For example, you can add another filter to select only adult testers:
public static void main(String[] args) {
List
testers = new ArrayList<>();
List
funTesters = testers.stream()
.filter(tester -> tester.getSoul().contains("Fun"))
.filter(tester -> tester.getAge() > 18)
.collect(Collectors.toList());
}Combining conditions yields more precise results.
Map Transformation
Beginner
The map operation transforms each element in a stream by applying a function (often a lambda expression), producing a new stream of the transformed results.
Simple example:
public static void main(String[] args) {
List
testers = new ArrayList<>();
List
souls = testers.stream()
.map(FunTester::getSoul)
.collect(Collectors.toList());
}This extracts the soul of each Tester and creates a list of String values.
Advanced
You can combine map with filter to process only non‑null values and trim whitespace:
public static void main(String[] args) {
List
testers = new ArrayList<>();
List
souls = testers.stream()
.map(FunTester::getSoul)
.filter(Objects::nonNull) // filter out null values
.map(String::trim) // remove leading/trailing spaces
.collect(Collectors.toList());
}The map operation is versatile and can be used for complex transformations beyond simple extraction.
Aggregation
Beginner
The reduce operation aggregates stream elements into a single result, useful for statistics or summarization. Example: calculate the total number of bugs across all testers.
public static void main(String[] args) {
List
testers = new ArrayList<>();
int bugs = testers.stream()
.mapToInt(FunTester::getBugs) // get bug count for each tester
.reduce(0, Integer::sum); // sum all bug counts
}This demonstrates a concise pattern for summing integer values extracted from objects.
Advanced
reduce can also be used to find the tester with the most bugs:
FunTester funTester = testers.stream()
.reduce((t1, t2) -> t1.getBugs() > t2.getBugs() ? t1 : t2)
.get();
System.out.println("BUG most tester: " + funTester.getName());The reduction compares bug counts and returns the tester with the highest number.
Grouping
Basic
The groupingBy collector groups stream elements based on a classifier function, allowing you to organize data for further analysis.
Example: group testers by years of service:
Map
> collect = testers.stream()
.collect(Collectors.groupingBy(FunTester::getCompanyYears));This creates a map where the key is the number of company years and the value is the list of testers with that tenure.
Advanced
Java Streams also support nested grouping. The following example first groups by company years, then within each group categorizes testers as "old" or "young" based on age:
public static void main(String[] args) {
List
testers = new ArrayList<>();
Map
>> collect = testers.stream()
.collect(Collectors.groupingBy(FunTester::getCompanyYears,
Collectors.groupingBy(t -> {
if (t.getAge() > 35) {
return "old age"; // age greater than 35
} else {
return "young age";
}
})));
}Nested groupingBy enables multi‑level data organization.
Potential of Java Streams
The capabilities of Java Streams go far beyond the examples shown. By exploring more features, you can improve code readability, maintainability, and efficiency, handling complex data transformations and flexible operations with ease.
FunTester
10k followers, 1k articles | completely useless
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.