Java — фильтрация потока с помощью лямбда-выражений PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

Java — фильтрация потока с помощью лямбда-выражений

Потоки Java были представлены еще в Java 8 в 2014 году, чтобы представить многословную Java в парадигме функционального программирования. Java Streams предоставляет множество гибких и мощных функциональных операций для выполнения обработки коллекций в одну строку.

Фильтрация коллекций по какому-либо предикату остается одной из наиболее часто используемых функциональных операций и может выполняться с помощью Predicate а точнее - с Лямбда-выражение.

В этом кратком руководстве мы рассмотрим, как фильтровать поток Java 8 с помощью лямбда-выражений.

Фильтрация потоков в Java

В общем, любой Stream можно фильтровать через filter() метод и заданный предикат:

Stream filter(Predicate<? super T> predicate)

Каждый элемент в потоке выполняется против предиката и добавляется в выходной поток, если предикат возвращает true. Вы можете предоставить Predicate пример:

Predicate contains = s -> s.contains("_deprecated");
List results = stream.filter(contains).collect(Collectors.toList());

Или упростите его, предоставив лямбда-выражение:

List results = stream.filter(s -> s.contains("_deprecated"))
                             .collect(Collectors.toList());

Или даже свернуть лямбда-выражение в ссылка на метод:


List results = stream.filter(String::isEmpty)
                             .collect(Collectors.toList());

Однако со ссылками на методы вы не можете передавать аргументы, но вы можете определять методы в фильтруемом объекте и настраивать их так, чтобы их было легко фильтровать (пока метод не принимает аргументы и возвращает значение). boolean).

Помните, что потоки не являются коллекциями - это потоки коллекций, и вам придется собирать их обратно в любую коллекцию, например List, Mapи т. д., чтобы придать им постоянство. Кроме того, все операции, выполняемые над элементами потока, либо промежуточный or терминал:

  • Промежуточные операции возвращают новый поток с изменениями по сравнению с предыдущей операцией.
  • Терминальные операции возвращают тип данных и предназначены для завершения конвейера обработки в потоке.

filter() есть промежуточный операция и предназначена для объединения с другими промежуточными операциями до завершения потока. Чтобы сохранить любые изменения (например, изменения самих элементов или отфильтрованных результатов), вам нужно будет присвоить результирующий выходной поток к новой ссылочной переменной через терминальную операцию.

Примечание: Даже при объединении множества лямбда-выражений вы можете не столкнуться с проблемами читаемости с правильными разрывами строк.

В следующих примерах мы будем работать с этим списком книг:

Book book1 = new Book("001", "Our Mathematical Universe", "Max Tegmark", 432, 2014);
Book book2 = new Book("002", "Life 3.0", "Max Tegmark", 280, 2017);
Book book3 = new Book("003", "Sapiens", "Yuval Noah Harari", 443, 2011);
        
List books = Arrays.asList(book1, book2, book3);

Коллекция фильтров с помощью Stream.filter()

Давайте отфильтруем эту коллекцию книг. Подойдет любой предикат — так что давайте, например, отфильтруем, какие книги имеют более 400 страниц:

List results = books.stream()
                          .filter(b -> b.getPageNumber() > 400)
                          .collect(Collectors.toList());

В результате получается список, который содержит:

[
Book{id='001', name='Our Mathematical Universe', author='Max Tegmark', pageNumber=432, publishedYear=2014}, 
Book{id='003', name='Sapiens', author='Yuval Noah Harari', pageNumber=443, publishedYear=2011}
]

При фильтрации действительно полезным методом цепочки является map(), который позволяет сопоставлять объекты с другим значением. Например, мы можем сопоставить каждой книге ее имя и, таким образом, вернуть только имена книг, подходящих под сказуемое из filter() вызов:

List results = books.stream()
                            .filter(b -> b.getPageNumber() > 400)
                            .map(Book::getName)
                            .collect(Collectors.toList());

В результате получается список строк:

[Our Mathematical Universe, Sapiens]

Коллекция фильтров для нескольких предикатов с помощью Stream.filter()

Обычно мы хотели бы фильтровать коллекции более чем по одному критерию. Это можно сделать, объединив несколько filter() звонки or используя предикат короткого замыкания, который проверяет два условия в одном filter() вызов.

 List results = books.stream()
                    .filter(b -> b.getPageNumber() > 400 && b.getName().length() > 10)
                    .collect(Collectors.toList());
                    


 List results2 = books.stream()
                    .filter(b -> b.getPageNumber() > 400)
                    .filter(b -> b.getName().length() > 10)
                    .collect(Collectors.toList());

Ознакомьтесь с нашим практическим руководством по изучению Git с рекомендациями, принятыми в отрасли стандартами и прилагаемой памяткой. Перестаньте гуглить команды Git и на самом деле изучить это!

При использовании нескольких критериев лямбда-вызовы могут стать несколько длинными. На этом этапе извлечение их как автономных предикатов может дать больше ясности. Однако какой подход быстрее?

Один фильтр со сложным условием или несколько фильтров?

Это зависит от вашего оборудования, размера вашей коллекции и от того, используете ли вы параллельные потоки или нет. Как правило, один фильтр со сложным условием превосходит несколько фильтров с более простыми условиями (коллекции от малого до среднего) или работает на том же уровне (коллекции очень большие). Если ваши условия слишком длинные, вам может быть полезно распределить их по нескольким filter() звонки, для улучшения читаемости, так как производительность очень похожа.

Лучший выбор — попробовать оба, обратите внимание на производительность на целевое устройствои соответствующим образом скорректируйте свою стратегию.

Пользователь GitHub волкодавы провел тест фильтрации в операциях/с пропускной способности и разместил результаты на «javafilters-бенчмарки» репозиторий. Результаты сведены в информативную таблицу:

Он показывает явное уменьшение доходности при больших размерах коллекций, при этом оба подхода работают примерно на одном уровне. Параллельные потоки значительно выигрывают при больших размерах коллекций, но ограничивают производительность при меньших размерах (менее ~10 тыс. элементов). Стоит отметить, что параллельные потоки поддерживают свою пропускную способность намного лучше, чем непараллельные потоки, что делает их значительно более устойчивыми к вводу.

Отметка времени:

Больше от Стекабьюс