Метод Stream.filter() в Java
Java Stream API, представленный в Java 8, предоставляет мощный способ работы с последовательностями элементов. Одним из наиболее полезных методов является filter(), который позволяет разработчикам обрабатывать коллекции и фильтровать данные на основе заданных критериев. В этой статье мы рассмотрим метод filter() из класса java.util.stream.Stream, объясним его использование и приведем практические примеры фильтрации пользовательских данных и обработки потоков объектов.
Потоки в Java и метод filter()
Java Streams являются неотъемлемой частью Java Collections Framework, представленной в Java 8. Они предоставляют современный способ обработки коллекций объектов, позволяя разработчикам выполнять операции с данными декларативным образом. В отличие от традиционных итерационных методов, использующих циклы, потоки позволяют выразить сложные задачи обработки данных в более удобочитаемой и лаконичной форме.
Представление о потоках Java
Поток в Java - это последовательность элементов, которая поддерживает различные операции для получения желаемого результата. Эти операции подразделяются на промежуточные и конечные:
- Промежуточные операции: эти операции преобразуют поток в другой поток и являются ленивыми, то есть они не выполняются до тех пор, пока не будет вызвана терминальная операция. В качестве примеров можно привести filter(), map(), sorted() и distinct().
- Терминальные операции: эти операции приводят к результату или побочному эффекту и запускают обработку потока. В качестве примеров можно привести collect(), forEach(), reduce() и count().
Потоки предназначены для обработки в конвейере, где каждый этап конвейера является либо промежуточной, либо конечной операцией. Такой подход обеспечивает более эффективную обработку и может использовать многоядерные архитектуры для параллельного выполнения.
Преимущества использования потоков
Использование потоков в Java дает ряд преимуществ:
- Лаконичный код: потоки позволяют вам кратко излагать сложные задачи по обработке данных.
- Улучшенная читабельность: декларативный характер потоков облегчает чтение и понимание кода.
- Параллельная обработка: потоки могут быть легко преобразованы в параллельные потоки, что позволяет выполнять параллельную обработку с минимальными усилиями.
- Сокращение шаблонности: потоки уменьшают потребность в шаблонном коде, таком как циклы и условные выражения, которые часто используются при традиционной обработке данных.
Метод filter()
Метод filter() является одной из наиболее часто используемых промежуточных операций в Stream API. Он позволяет создать новый поток, содержащий только те элементы, которые соответствуют заданному предикату. Предикат - это функциональный интерфейс, который представляет условие или проверку входного значения, возвращая значение true или false.
Сигнатура метода filter() выглядит следующим образом:
Stream<T> filter(Predicate<? super T> predicate);
Здесь T обозначает тип элементов в потоке, а Predicate<? super T> - это условие, используемое для фильтрации элементов.
Когда вы вызываете метод filter() для потока, он не обрабатывает элементы немедленно. Вместо этого он возвращает новый поток, который будет включать только те элементы, которые удовлетворяют предикату при выполнении терминальной операции.
Пример использования filter()
Чтобы показать использование метода filter(), рассмотрим простой пример, когда у нас есть список целых чисел, и мы хотим отфильтровать четные числа.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FilterExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("Четные числа: " + evenNumbers);
}
}
В этом примере метод filter() используется для сохранения только четных чисел из списка. Предикат n -> n % 2 == 0 проверяет каждое число, и в результирующий список включаются только те, которые делятся на 2. Функция collect(Collectors.toList()) - это терминальная операция, которая собирает отфильтрованные элементы в новый список.
Объединение нескольких операций в цепочку
Одной из мощных функций при работе с потоками является возможность объединения нескольких операций в цепочку. Например, вы можете комбинировать filter() с другими промежуточными операциями, такими как map() и sorted(), для выполнения сложной обработки данных в едином конвейере.
Рассмотрим следующий пример, в котором у нас есть список строк, и мы хотим отфильтровать строки, начинающиеся с буквы A, преобразовать оставшиеся строки в верхний регистр и отсортировать их по алфавиту:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamChainingExample {
public static void main(String[] args) {
List<String> strings = Arrays.asList("Apple", "Banana", "Avocado", "Cherry", "Apricot");
List<String> result = strings.stream()
.filter(s -> !s.startsWith("A"))
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
System.out.println("Отфильтрованные и обработанные строки: " + result);
}
}
В этом примере мы объединяем операции filter(), map() и sorted() в цепочку для достижения желаемого результата. Метод filter() удаляет строки, начинающиеся с A, метод map() преобразует оставшиеся строки в верхний регистр, а метод sorted() сортирует их по алфавиту.
Использование filter() для обработки коллекций
Метод filter() является ключевым компонентом Java Stream API, позволяющим разработчикам обрабатывать коллекции, применяя предикат к каждому элементу в потоке. Здесь мы рассмотрим, как работает метод filter(), лежащие в его основе механики и его применение в реальных сценариях.
Как filter() работает внутри системы
Когда в потоке вызывается метод filter(), он создает новый поток с промежуточной операцией, в которой сохраняется предикат. Этот новый поток не обрабатывает никаких элементов, пока не будет вызвана терминальная операция. Фактическая фильтрация происходит во время выполнения терминальной операции, такой как collect(), forEach() или reduce().
Например, рассмотрим следующий фрагмент кода, который фильтрует список целых чисел, чтобы сохранить только четные числа:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FilterExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("Четные числа: " + evenNumbers);
}
}
В этом примере:
- Создание потока: вызов numbers.stream() создает поток из списка номеров.
- Промежуточная операция filter(): вызов filter(n -> n % 2 == 0) добавляет операцию фильтрации в конвейер потока. Предикат n -> n % 2 == 0 проверяет, является ли каждое число четным.
- Терминальная операция collect(): вызов collect(сборщики.ToList()) запускает выполнение потокового конвейера. Во время этого выполнения каждый элемент проходит через операцию filter(), и только четные номера собираются в новый список.
Ленивые (отложенные) вычисления
Одним из основных принципов Java Streams являются ленивые вычисления. Промежуточные операции, такие как filter(), выполняются не сразу. Вместо этого они записываются и выполняются только при вызове терминальной операции. Эта отложенная обработка обеспечивает эффективную обработку и оптимизацию конвейера потока.
Например, если у вас большая коллекция и вы хотите отфильтровать и ограничить результаты, потоковый конвейер обрабатывает только необходимые элементы:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class LazyEvaluationExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> firstTwoEvenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.limit(2)
.collect(Collectors.toList());
System.out.println("Первые два четных числа: " + firstTwoEvenNumbers);
}
}
В этом примере операция limit(2) гарантирует, что поток обрабатывает только первые два четных числа, избегая ненужных вычислений для остальных элементов.
Комбинирование функции filter() с другими операциями
Истинная мощь метода filter() проявляется в сочетании с другими потоковыми операциями. Объединяя несколько операций в цепочку, вы можете выполнять сложные преобразования данных в рамках одного конвейера.
Рассмотрим пример, когда у вас есть список людей, и вы хотите отфильтровать тех, кому еще нет 18, а затем отсортировать оставшихся людей по их именам:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public class StreamExample {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Вася", 22),
new Person("Игорь", 17),
new Person("Света", 19),
new Person("Петя", 15)
);
List<Person> adultsSortedByName = people.stream()
.filter(person -> person.getAge() >= 18)
.sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))
.collect(Collectors.toList());
System.out.println("Совершеннолетние отсортированы по имени: " + adultsSortedByName);
}
}
В этом примере:
- Создание потока: вызов people.stream() создает поток из списка людей.
- Промежуточная операция filter(): вызов filter(person -> person.getAge() >= 18) отфильтровывает людей младше 18 лет.
- Промежуточная операция sorted(): вызов sorted((p1, p2) -> p1.getName().compareTo(p2.getName())) сортирует оставшихся пользователей по их именам.
- Операция терминала collect(): вызов collect(Collectors.toList()) собирает отфильтрованных и отсортированных пользователей в новый список.
Практические примеры
В этом разделе мы рассмотрим практические примеры использования метода filter() для обработки различных типов коллекций. Эти примеры продемонстрируют, как фильтровать данные в реальных сценариях, таких как обработка потоков заказов для выявления транзакций с высокой стоимостью и работа с вложенными коллекциями.
Пример 1: Выявление сделок с высокой стоимостью
Давайте рассмотрим сценарий, в котором у нас есть список заказов, и мы хотим отфильтровать транзакции с высокой стоимостью. В этом примере транзакция с высокой стоимостью определяется как транзакция с суммой, превышающей 1000 долларов.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
class Order {
private String orderId;
private double amount;
public Order(String orderId, double amount) {
this.orderId = orderId;
this.amount = amount;
}
public double getAmount() {
return amount;
}
public String getOrderId() {
return orderId;
}
@Override
public String toString() {
return "Order{orderId='" + orderId + "', amount=" + amount + "}";
}
}
public class HighValueTransactionExample {
public static void main(String[] args) {
List<Order> orders = Arrays.asList(
new Order("O1", 1500.0),
new Order("O2", 500.0),
new Order("O3", 1200.0),
new Order("O4", 300.0),
new Order("O5", 2000.0)
);
List<Order> highValueOrders = orders.stream()
.filter(order -> order.getAmount() > 1000)
.collect(Collectors.toList());
System.out.println("Ценные заказы: " + highValueOrders);
}
}
В этом примере мы фильтруем список заказов, чтобы сохранить только те, сумма которых превышает 1000 долларов. Это иллюстрирует, как filter() можно использовать для выявления транзакций с высокой стоимостью в списке заказов.
Пример 2. Фильтрация вложенных коллекций
В некоторых случаях может потребоваться фильтрация вложенных коллекций. Например, рассмотрим сценарий, в котором у каждого пользователя есть список заказов, и мы хотим отфильтровать пользователей на основе того, есть ли у них заказы с высокой стоимостью.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
class Order {
private String orderId;
private double amount;
public Order(String orderId, double amount) {
this.orderId = orderId;
this.amount = amount;
}
public double getAmount() {
return amount;
}
public String getOrderId() {
return orderId;
}
@Override
public String toString() {
return "Order{orderId='" + orderId + "', amount=" + amount + "}";
}
}
class User {
private String name;
private List<Order> orders;
public User(String name, List<Order> orders) {
this.name = name;
this.orders = orders;
}
public List<Order> getOrders() {
return orders;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "User{name='" + name + "', orders=" + orders + "}";
}
}
public class NestedCollectionFilterExample {
public static void main(String[] args) {
List<User> users = Arrays.asList(
new User("Иван", Arrays.asList(new Order("O1", 1500.0), new Order("O2", 500.0))),
new User("Сергей", Arrays.asList(new Order("O3", 800.0), new Order("O4", 300.0))),
new User("Василий", Arrays.asList(new Order("O5", 2000.0), new Order("O6", 300.0))),
new User("Светлана", Arrays.asList(new Order("O7", 100.0), new Order("O8", 150.0)))
);
List<User> usersWithHighValueOrders = users.stream()
.filter(user -> user.getOrders().stream().anyMatch(order -> order.getAmount() > 1000))
.collect(Collectors.toList());
System.out.println("Пользователи с дорогостоящими заказами: " + usersWithHighValueOrders);
}
}
В этом примере мы используем метод anyMatch(), чтобы проверить, есть ли в списке заказов пользователя заказы на сумму, превышающую 1000 долларов. В этом примере показано, как использовать filter() в сочетании с другими потоковыми операциями для обработки вложенных коллекций.
Пример 3. Фильтрация сотрудников по отделам и зарплате
Рассмотрим сценарий, в котором у нас есть список сотрудников, и мы хотим отфильтровать сотрудников, которые принадлежат к определенному отделу и имеют зарплату выше определенного порога.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
class Employee {
private String name;
private String department;
private double salary;
public Employee(String name, String department, double salary) {
this.name = name;
this.department = department;
this.salary = salary;
}
public String getDepartment() {
return department;
}
public double getSalary() {
return salary;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Employee{name='" + name + "', department='" + department + "', salary=" + salary + "}";
}
}
public class DepartmentSalaryFilterExample {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("Иван", "HR", 60000.0),
new Employee("Сергей", "Engineering", 90000.0),
new Employee("Вероника", "HR", 50000.0),
new Employee("Светлана", "Engineering", 120000.0),
new Employee("Марина", "Sales", 70000.0)
);
List<Employee> highPaidEngineers = employees.stream()
.filter(employee -> "Engineering".equals(employee.getDepartment()))
.filter(employee -> employee.getSalary() > 80000)
.collect(Collectors.toList());
System.out.println("Высокооплачиваемые инженеры: " + highPaidEngineers);
}
}
В этом примере мы фильтруем список сотрудников, чтобы сохранить только тех, кто работает в инженерном отделе и имеет зарплату более 80000 долларов. Это демонстрирует, как метод filter() можно использовать для применения нескольких критериев для фильтрации сложных объектов.
Заключение
Метод filter() в Java Stream API - это универсальный и мощный инструмент для обработки и фильтрации коллекций на основе определенных критериев. Понимая, как эффективно использовать метод filter(), разработчики могут писать более сжатый, читаемый и поддерживаемый код. Приведенные примеры демонстрируют, как фильтровать данные в реальных сценариях, от простых списков целых чисел до сложных вложенных коллекций и фильтрации по нескольким критериям.
Благодаря возможности связывать несколько операций в цепочку, использовать отложенные вычисления и применять предикаты, метод filter() обеспечивает эффективную и гибкую обработку данных. Независимо от того, занимаетесь ли вы фильтрацией пользовательских данных, выявлением дорогостоящих транзакций или работаете с вложенными коллекциями, освоение метода filter() значительно улучшит вашу способность обрабатывать коллекции и манипулировать ими в ваших Java-приложениях.
Комментарии
Для того чтобы оставить свое мнение, необходимо зарегистрироваться на сайте