Stream Filtering in Java

Introduction

Imagine you have a big box of mixed fruits (your data), but you only want the apples. Instead of picking through each fruit one by one, you use a special "apple-only" filter. Stream Filtering in Java does exactly this—it helps you easily select only the elements you want from a collection of data.

Stream filtering is a powerful and readable way to process collections by applying conditions to include or exclude elements. It's like having a smart sieve that only lets through what you need!


What is Stream Filtering?

Stream filtering is the process of selecting elements from a stream that match a given condition (predicate). It's one of the most common and useful operations when working with Java Streams.

Key Concepts:

  • Filter: Intermediate operation that takes a Predicate (boolean condition)
  • Predicate: A function that returns true or false for each element
  • Non-destructive: Original data remains unchanged
  • Lazy: Filtering doesn't happen until a terminal operation is called

Code Explanation with Examples

Example 1: Basic Filtering with Numbers

import java.util.*;
import java.util.stream.*;
public class BasicFiltering {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Filter even numbers only
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)  // Keep only even numbers
.collect(Collectors.toList());
System.out.println("Even numbers: " + evenNumbers);
// Output: Even numbers: [2, 4, 6, 8, 10]
// Filter numbers greater than 5
List<Integer> greaterThanFive = numbers.stream()
.filter(n -> n > 5)
.collect(Collectors.toList());
System.out.println("Numbers > 5: " + greaterThanFive);
// Output: Numbers > 5: [6, 7, 8, 9, 10]
}
}

Example 2: Filtering Objects with Multiple Conditions

import java.util.*;
import java.util.stream.*;
class Person {
String name;
int age;
String city;
Person(String name, int age, String city) {
this.name = name;
this.age = age;
this.city = city;
}
public String toString() {
return name + " (" + age + ") from " + city;
}
}
public class ObjectFiltering {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 25, "New York"),
new Person("Bob", 17, "Boston"),
new Person("Charlie", 30, "New York"),
new Person("Diana", 16, "Chicago"),
new Person("Eve", 28, "Boston")
);
// Filter adults from New York
List<Person> nyAdults = people.stream()
.filter(p -> p.age >= 18)           // Must be adult
.filter(p -> p.city.equals("New York")) // Must be from NY
.collect(Collectors.toList());
System.out.println("Adults from New York:");
nyAdults.forEach(System.out::println);
// Output: 
// Alice (25) from New York
// Charlie (30) from New York
// Combine conditions in one filter
List<Person> bostonTeens = people.stream()
.filter(p -> p.age < 18 && p.city.equals("Boston"))
.collect(Collectors.toList());
System.out.println("\nTeens from Boston:");
bostonTeens.forEach(System.out::println);
// Output: Bob (17) from Boston
}
}

Example 3: String Filtering

import java.util.*;
import java.util.stream.*;
public class StringFiltering {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry", "date", 
"elderberry", "fig", "grape");
// Filter words starting with 'a'
List<String> startsWithA = words.stream()
.filter(word -> word.startsWith("a"))
.collect(Collectors.toList());
System.out.println("Words starting with 'a': " + startsWithA);
// Output: [apple]
// Filter words with length > 5
List<String> longWords = words.stream()
.filter(word -> word.length() > 5)
.collect(Collectors.toList());
System.out.println("Long words: " + longWords);
// Output: [banana, cherry, elderberry]
// Filter words containing 'e'
List<String> containsE = words.stream()
.filter(word -> word.contains("e"))
.collect(Collectors.toList());
System.out.println("Words containing 'e': " + containsE);
// Output: [apple, cherry, date, elderberry, grape]
}
}

Example 4: Using Complex Conditions with Method References

import java.util.*;
import java.util.stream.*;
import java.util.function.Predicate;
public class ComplexFiltering {
public static void main(String[] args) {
List<String> data = Arrays.asList("Java", "Python", "JavaScript", 
"C++", "Ruby", "Go", "Swift");
// Create reusable predicates
Predicate<String> startsWithJ = s -> s.startsWith("J");
Predicate<String> longName = s -> s.length() > 4;
Predicate<String> containsA = s -> s.toLowerCase().contains("a");
// Filter languages starting with 'J'
List<String> jLanguages = data.stream()
.filter(startsWithJ)
.collect(Collectors.toList());
System.out.println("Languages starting with J: " + jLanguages);
// Output: [Java, JavaScript]
// Filter long languages that contain 'a'
List<String> longLanguagesWithA = data.stream()
.filter(longName.and(containsA))  // Combine conditions
.collect(Collectors.toList());
System.out.println("Long languages with 'a': " + longLanguagesWithA);
// Output: [JavaScript]
// Filter languages that start with J OR are short
List<String> jOrShort = data.stream()
.filter(startsWithJ.or(s -> s.length() <= 2))
.collect(Collectors.toList());
System.out.println("J languages or short ones: " + jOrShort);
// Output: [Java, JavaScript, Go]
}
}

Example 5: Real-World Practical Examples

import java.util.*;
import java.util.stream.*;
class Product {
String name;
double price;
String category;
int stock;
Product(String name, double price, String category, int stock) {
this.name = name;
this.price = price;
this.category = category;
this.stock = stock;
}
public String toString() {
return String.format("%s: $%.2f (%d in stock)", name, price, stock);
}
}
public class PracticalExamples {
public static void main(String[] args) {
List<Product> products = Arrays.asList(
new Product("Laptop", 999.99, "Electronics", 5),
new Product("Phone", 699.99, "Electronics", 0),
new Product("Book", 19.99, "Education", 50),
new Product("Chair", 149.99, "Furniture", 10),
new Product("Desk", 299.99, "Furniture", 0),
new Product("Pen", 1.99, "Education", 100)
);
// Filter available electronics
List<Product> availableElectronics = products.stream()
.filter(p -> p.category.equals("Electronics"))
.filter(p -> p.stock > 0)
.collect(Collectors.toList());
System.out.println("Available Electronics:");
availableElectronics.forEach(System.out::println);
// Filter expensive products that are out of stock
List<Product> expensiveOutOfStock = products.stream()
.filter(p -> p.price > 100)
.filter(p -> p.stock == 0)
.collect(Collectors.toList());
System.out.println("\nExpensive out-of-stock items:");
expensiveOutOfStock.forEach(System.out::println);
// Filter cheap products with good stock
List<Product> cheapWellStocked = products.stream()
.filter(p -> p.price < 20 && p.stock >= 10)
.collect(Collectors.toList());
System.out.println("\nCheap well-stocked items:");
cheapWellStocked.forEach(System.out::println);
}
}

Example 6: Filtering with findFirst() and findAny()

import java.util.*;
import java.util.stream.*;
public class FindOperations {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Find first even number
Optional<Integer> firstEven = numbers.stream()
.filter(n -> n % 2 == 0)
.findFirst();
firstEven.ifPresent(n -> System.out.println("First even: " + n));
// Output: First even: 2
// Find any number divisible by 5
Optional<Integer> anyDivisibleBy5 = numbers.stream()
.filter(n -> n % 5 == 0)
.findAny();
anyDivisibleBy5.ifPresent(n -> System.out.println("Number divisible by 5: " + n));
// Output: Number divisible by 5: 5
}
}

Common Filtering Patterns

Pattern 1: Chaining Multiple Filters

list.stream()
.filter(condition1)
.filter(condition2)
.filter(condition3)
.collect(Collectors.toList());

Pattern 2: Combining Conditions

list.stream()
.filter(condition1.and(condition2))  // AND operation
.filter(condition1.or(condition2))   // OR operation
.filter(condition1.negate())         // NOT operation
.collect(Collectors.toList());

Pattern 3: Using Method References

list.stream()
.filter(Objects::nonNull)        // Filter out nulls
.filter(String::isEmpty)         // Filter empty strings
.collect(Collectors.toList());

Best Practices

  1. Keep Predicates Simple: Break complex conditions into multiple filters or use named predicates
  2. Filter Early: Apply filters as early as possible in the stream pipeline
  3. Use Method References: When appropriate, for better readability
  4. Handle Null Safety: Use Objects::nonNull to avoid NullPointerException
  5. Reuse Predicates: Create reusable predicates for common conditions

Conclusion

Stream filtering is like having a smart search assistant for your collections. It helps you:

  • Find exactly what you need with simple conditions
  • Combine multiple criteria easily
  • Write clean, readable code that expresses intent clearly
  • Process data efficiently without modifying the original collection

Whether you're working with numbers, strings, or complex objects, stream filtering makes data selection intuitive and powerful. It's one of the most essential tools in modern Java programming!

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper