- Stream: A sequence of elements supporting functional-style operations.
- filter(): An intermediate operation that takes a Predicate (condition) and returns a new stream with elements that satisfy the condition.
- Predicate: A functional interface (java.util.function.Predicate) with a test() method returning true or false.
- Terminal Operation: Required to produce a result (e.g., collect(), forEach(), findFirst()).
Syntax
stream.filter(predicate).terminalOperation();
predicate: A lambda expression or method reference (e.g., x -> x > 5).
terminalOperation: Converts the filtered stream into a result (e.g., collect(Collectors.toList())).
Code language: CSS (css)
Filtering Operations
- Single filter(): Select elements based on one condition.
- Chained filter(): Apply multiple conditions sequentially.
- Combined Predicates: Use Predicate methods (and(), or(), negate()) for complex logic.
- Short-Circuiting: Use operations like findFirst() or findAny() to stop after finding a match.
1. Single Filter
Filter a list to keep only numbers greater than 5:
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class SingleFilter { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(2, 7, 4, 9, 1, 6); List<Integer> greaterThanFive = numbers.stream() .filter(n -> n > 5) .collect(Collectors.toList()); System.out.println(greaterThanFive); // Output: [7, 9, 6] } }
2. Chained Filters
Filter strings that start with “A” and have length greater than 3:
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class ChainedFilters { public static void main(String[] args) { List<String> names = Arrays.asList("Ananth", "Raj", "Aravind", "Abi"); List<String> filteredNames = names.stream() .filter(s -> s.startsWith("A")) .filter(s -> s.length() > 3) .collect(Collectors.toList()); System.out.println(filteredNames); // Output: [Ananth, Aravind] } }
3. Combined Predicates
Filter numbers that are even and less than 10:
import java.util.Arrays; import java.util.List; import java.util.function.Predicate; import java.util.stream.Collectors; public class CombinedPredicates { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(2, 3, 4, 8, 12, 6, 9); Predicate<Integer> isEven = n -> n % 2 == 0; Predicate<Integer> lessThanTen = n -> n < 10; List<Integer> result = numbers.stream() .filter(isEven.and(lessThanTen)) .collect(Collectors.toList()); System.out.println(result); // Output: [2, 4, 8, 6] } }
4. Filtering Objects with Short-Circuiting
Filter a list of Employee objects to find the first employee with a salary above 50000:
import java.util.Arrays; import java.util.List; class Employee { String name; double salary; Employee(String name, double salary) { this.name = name; this.salary = salary; } @Override public String toString() { return name + "(" + salary + ")"; } } public class ObjectFiltering { public static void main(String[] args) { List<Employee> employees = Arrays.asList( new Employee("Aravind", 45000), new Employee("Akhil", 60000), new Employee("Amruth", 70000) ); Employee highEarner = employees.stream() .filter(e -> e.salary > 50000) .findFirst() .orElse(null); System.out.println(highEarner); // Output: Akhil(60000) } }
Additional Filtering Operations
Additional Filtering Operations
- Distinct Filtering (distinct()): Removes duplicates from a stream, effectively filtering out non-unique elements.
- Limit and Skip (limit(), skip()): Filters a stream by restricting the number of elements (limit()) or skipping initial elements (skip()).
- Filtering with Complex Predicates: Use custom or combined predicates for sophisticated filtering logic.
- Filtering with takeWhile() and dropWhile(): Conditionally take or drop elements based on a predicate (introduced in Java 9, but relevant for modern Java).
- Filtering with Mapping (filter() + map()): Combine filtering with transformation to process data.
- Short-Circuiting Filters (findAny(), findFirst(), anyMatch(), allMatch(), noneMatch()): Optimize filtering by stopping early when a condition is met.
Examples
1. Distinct Filtering
Remove duplicate elements from a list:
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class DistinctFiltering { public static void main(String[] args) { List<String> names = Arrays.asList("Alice", "Bob", "Alice", "Charlie", "Bob"); List<String> uniqueNames = names.stream() .distinct() .collect(Collectors.toList()); System.out.println(uniqueNames); // Output: [Alice, Bob, Charlie] } }
2. Limit and Skip
- Limit: Filter to keep only the first n elements.
- Skip: Filter out the first n elements.
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class LimitSkipFiltering { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8); // Keep only the first 3 elements List<Integer> limited = numbers.stream() .limit(3) .collect(Collectors.toList()); System.out.println(limited); // Output: [1, 2, 3] // Skip the first 3 elements List<Integer> skipped = numbers.stream() .skip(3) .collect(Collectors.toList()); System.out.println(skipped); // Output: [4, 5, 6, 7, 8] // Combine limit and skip List<Integer> combined = numbers.stream() .skip(2) .limit(3) .collect(Collectors.toList()); System.out.println(combined); // Output: [3, 4, 5] } }
3. Complex Predicates
Filter a list of objects based on multiple conditions using a custom predicate:
import java.util.Arrays; import java.util.List; import java.util.function.Predicate; import java.util.stream.Collectors; class Product { String name; double price; boolean inStock; Product(String name, double price, boolean inStock) { this.name = name; this.price = price; this.inStock = inStock; } @Override public String toString() { return name + "(" + price + ")"; } } public class ComplexPredicateFiltering { public static void main(String[] args) { List<Product> products = Arrays.asList( new Product("Laptop", 1000.0, true), new Product("Phone", 500.0, false), new Product("Tablet", 300.0, true), new Product("Monitor", 200.0, true) ); Predicate<Product> isAffordable = p -> p.price < 600.0; Predicate<Product> isAvailable = p -> p.inStock; List<Product> affordableAvailable = products.stream() .filter(isAffordable.and(isAvailable)) .collect(Collectors.toList()); System.out.println(affordableAvailable); // Output: [Tablet(300.0), Monitor(200.0)] } }
4. takeWhile and dropWhile (Java 9+)
- takeWhile(): Takes elements while the predicate is true, stopping at the first false.
- dropWhile(): Drops elements while the predicate is true, then takes the rest.
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class TakeDropWhileFiltering { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(2, 4, 6, 7, 8, 10); // Take elements while they are even List<Integer> taken = numbers.stream() .takeWhile(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(taken); // Output: [2, 4, 6] // Drop elements while they are even List<Integer> dropped = numbers.stream() .dropWhile(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(dropped); // Output: [7, 8, 10] } }
5. Filtering with Mapping
Filter and transform data in one pipeline (e.g., extract names of valid users):
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; class User { String name; boolean active; User(String name, boolean active) { this.name = name; this.active = active; } } public class FilterMap { public static void main(String[] args) { List<User> users = Arrays.asList( new User("Alice", true), new User("Bob", false), new User("Charlie", true) ); List<String> activeUserNames = users.stream() .filter(u -> u.active) .map(u -> u.name) .collect(Collectors.toList()); System.out.println(activeUserNames); // Output: [Alice, Charlie] } }
6. Short-Circuiting Filters
Use anyMatch(), allMatch(), or noneMatch() to check conditions without collecting all results:
import java.util.Arrays; import java.util.List; public class ShortCircuitFiltering { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // Check if any number is even boolean hasEven = numbers.stream() .anyMatch(n -> n % 2 == 0); System.out.println("Has even number: " + hasEven); // Output: Has even number: true // Check if all numbers are positive boolean allPositive = numbers.stream() .allMatch(n -> n > 0); System.out.println("All positive: " + allPositive); // Output: All positive: true // Check if no number is negative boolean noNegative = numbers.stream() .noneMatch(n -> n < 0); System.out.println("No negative: " + noNegative); // Output: No negative: true // Find any number greater than 3 Integer anyGreaterThanThree = numbers.stream() .filter(n -> n > 3) .findAny() .orElse(null); System.out.println("Any > 3: " + anyGreaterThanThree); // Output: Any > 3: 4 (or another number > 3) } }
Best Practices
- Use distinct() sparingly, as it may require additional memory for tracking unique elements.
- Combine limit() and skip() for pagination-like scenarios.
- Prefer takeWhile()/dropWhile() for ordered streams with conditional breaks.
- Use short-circuiting operations to avoid unnecessary processing.
Filtering operations in Java Streams provide a powerful mechanism for selecting elements based on specific criteria. By leveraging predicates and chaining methods like filter(), distinct(), limit(), and skip(), developers can efficiently process data streams while maintaining clean and expressive code. This approach enhances productivity and readability, making Java Streams a valuable tool for modern data processing tasks.