0 added
0 removed
Original
2026-01-01
Modified
2026-02-21
1
<p><a>#статьи</a></p>
1
<p><a>#статьи</a></p>
2
<ul><li>30 апр 2021</li>
2
<ul><li>30 апр 2021</li>
3
<li>0</li>
3
<li>0</li>
4
</ul><p>Если вы не любите стримы, возможно, вы пока не умеете их готовить :) Приглашаем поучиться.</p>
4
</ul><p>Если вы не любите стримы, возможно, вы пока не умеете их готовить :) Приглашаем поучиться.</p>
5
<p>Фулстек-разработчик. Любимый стек: Java + Angular, но в хорошей компании готова писать хоть на языке Ада.</p>
5
<p>Фулстек-разработчик. Любимый стек: Java + Angular, но в хорошей компании готова писать хоть на языке Ада.</p>
6
<p>В этой статье почти нет теории, зато много практики и кода. Разберём семь типичных ситуаций, когда стримы бывают полезны. Сравним решения с классическими императивными реализациями.</p>
6
<p>В этой статье почти нет теории, зато много практики и кода. Разберём семь типичных ситуаций, когда стримы бывают полезны. Сравним решения с классическими императивными реализациями.</p>
7
<p>Это способ работать со структурами данных Java, чаще всего<a>коллекциями</a>, в стиле функциональных языков программирования.</p>
7
<p>Это способ работать со структурами данных Java, чаще всего<a>коллекциями</a>, в стиле функциональных языков программирования.</p>
8
<p>О началах функционального программирования и лямбдах в Java читайте<a>здесь</a>.</p>
8
<p>О началах функционального программирования и лямбдах в Java читайте<a>здесь</a>.</p>
9
<p>Стрим - это объект для универсальной работы с данными. И это вовсе не какая-то новая структура данных, он использует существующие коллекции для получения новых элементов.</p>
9
<p>Стрим - это объект для универсальной работы с данными. И это вовсе не какая-то новая структура данных, он использует существующие коллекции для получения новых элементов.</p>
10
<p>Затем к данным применяются методы. В интерфейсе Stream их множество. Каждый выполняет одну из типичных операций с коллекцией: отсортировать, перегруппировать, отфильтровать. Мы разберём некоторые из этих методов дальше.</p>
10
<p>Затем к данным применяются методы. В интерфейсе Stream их множество. Каждый выполняет одну из типичных операций с коллекцией: отсортировать, перегруппировать, отфильтровать. Мы разберём некоторые из этих методов дальше.</p>
11
<p><strong>Думайте о стриме как о потоке данных, а о цепочке вызовов методов - как о конвейере.</strong></p>
11
<p><strong>Думайте о стриме как о потоке данных, а о цепочке вызовов методов - как о конвейере.</strong></p>
12
<p>Каждый промежуточный метод получает на вход результат выполнения с предыдущего этапа (стрим), отвечает только за свою часть работы и возвращает стрим.</p>
12
<p>Каждый промежуточный метод получает на вход результат выполнения с предыдущего этапа (стрим), отвечает только за свою часть работы и возвращает стрим.</p>
13
<p>Последний (терминальный) метод либо не возвращает значения (void), либо возвращает результат иного, нежели стрим, типа.</p>
13
<p>Последний (терминальный) метод либо не возвращает значения (void), либо возвращает результат иного, нежели стрим, типа.</p>
14
<p>Стримы избавляют программистов от написания стереотипного кода всякий раз, когда нужно сделать что-то с набором элементов. То есть благодаря стримам не приходится думать о деталях реализации.</p>
14
<p>Стримы избавляют программистов от написания стереотипного кода всякий раз, когда нужно сделать что-то с набором элементов. То есть благодаря стримам не приходится думать о деталях реализации.</p>
15
<p>Есть и другие плюсы:</p>
15
<p>Есть и другие плюсы:</p>
16
<ul><li>Стримы поддерживают один из основных принципов хорошего проектирования - слабую связанность (<strong>low coupling</strong>). Чем меньше класс знает про другие классы - тем лучше. Алгоритму сортировки не должно быть важно, что конкретно он сортирует. Это и делают стримы.</li>
16
<ul><li>Стримы поддерживают один из основных принципов хорошего проектирования - слабую связанность (<strong>low coupling</strong>). Чем меньше класс знает про другие классы - тем лучше. Алгоритму сортировки не должно быть важно, что конкретно он сортирует. Это и делают стримы.</li>
17
<li>С помощью стримов операции с коллекциями проще распараллелить: в императивном подходе для этого бы понадобился минимум ещё один цикл.</li>
17
<li>С помощью стримов операции с коллекциями проще распараллелить: в императивном подходе для этого бы понадобился минимум ещё один цикл.</li>
18
<li>Стримы позволяют уменьшить число побочных эффектов: методы Stream API не меняют исходные коллекции.</li>
18
<li>Стримы позволяют уменьшить число побочных эффектов: методы Stream API не меняют исходные коллекции.</li>
19
<li>Со Stream API лаконично записываются сложные алгоритмы обработки данных.</li>
19
<li>Со Stream API лаконично записываются сложные алгоритмы обработки данных.</li>
20
</ul><p>А теперь, когда вы почти поверили, что стримы - это хорошо, перейдём к практике.</p>
20
</ul><p>А теперь, когда вы почти поверили, что стримы - это хорошо, перейдём к практике.</p>
21
<p>Работу методов Java Stream API покажем на примере офлайновой библиотеки. Для каждой книги библиотечного фонда известны автор, название и год издания.</p>
21
<p>Работу методов Java Stream API покажем на примере офлайновой библиотеки. Для каждой книги библиотечного фонда известны автор, название и год издания.</p>
22
<p>Для читателя библиотеки будем хранить Ф. И. О. и электронный адрес. Каждый читатель может взять в библиотеке одну или несколько книг - их тоже сохраним.</p>
22
<p>Для читателя библиотеки будем хранить Ф. И. О. и электронный адрес. Каждый читатель может взять в библиотеке одну или несколько книг - их тоже сохраним.</p>
23
<p>Ещё нам понадобится флаг читательского согласия на уведомления по электронной почте. Рассылки организуют сотрудники библиотеки: напоминают о сроке возврата книг, сообщают новости.</p>
23
<p>Ещё нам понадобится флаг читательского согласия на уведомления по электронной почте. Рассылки организуют сотрудники библиотеки: напоминают о сроке возврата книг, сообщают новости.</p>
24
<p>Вот как это выглядит на Java:</p>
24
<p>Вот как это выглядит на Java:</p>
25
import java.util.Objects; public class Book { private String author; //Автор private String name; //Название private Integer issueYear; //Год издания public Book(String author, String name, Integer issueYear) { this.author = author; this.name = name; this.issueYear = issueYear; } public String getAuthor() { return author; } public String getName() { return name; } public Integer getIssueYear() { return issueYear; } @Override public String toString() { return "Book{" + "author='" + author + '\'' + ", name='" + name + '\'' + ", issueYear=" + issueYear + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Book book = (Book) o; return author.equals(book.author) && name.equals(book.name) && issueYear.equals(book.issueYear); } @Override public int hashCode() { return Objects.hash(author, name, issueYear); } }Класс для книг - Bookimport java.util.ArrayList; import java.util.List; public class Reader { private String fio; //Ф. И. О. private String email; //электронный адрес private boolean subscriber; //флаг согласия на рассылку private List<Book> books; //взятые книги public Reader(String fio, String email, boolean subscriber) { this.fio = fio; this.email = email; this.subscriber = subscriber; this.books = new ArrayList<>(); } public boolean isSubscriber() { return subscriber; } public String getFio() { return fio; } public String getEmail() { return email; } public List<Book> getBooks() { return books; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Reader reader = (Reader) o; return fio.equals(reader.fio); } @Override public int hashCode() { return Objects.hash(fio); } } Класс для читателей - Readerpublic class EmailAddress { private String email; //электронный адрес private String someData; /*доп. информация для формирования письма. В примерах не используем - добавили, чтобы оправдать существование отдельного класса :)*/ public EmailAddress(String email) { this.email = email; } public String getEmail() { return email; } public String getSomeData() { return someData; } public void setSomeData(String someData) { this.someData = someData; } }Класс для адреса рассылкиimport java.util.ArrayList; import java.util.List; public class Library { private List<Book> books; private List<Reader> readers; public Library() { init(); } private void init() { books = new ArrayList<>(); books.add(new Book("Оруэлл", "1984", 2021)); //и так далее для других книг readers = new ArrayList<>(); readers.add(new Reader("Иванов Иван Иванович", "ivanov.email@test.ru", true)); //и так далее для других читателей readers.get(0).getBooks().add(books.get(1)); //и так далее для других читателей и взятых книг } public List<Book> getBooks() { return books; } public List<Reader> getReaders() { return readers; } }Класс библиотеки (Library) с примером заполнения данных<p>Этот метод используется для сортировки элементов стрима. По умолчанию применяется сортировка по возрастанию (с числами всё понятно, а вот заглавные и строчные буквы рассматриваются отдельно).</p>
25
import java.util.Objects; public class Book { private String author; //Автор private String name; //Название private Integer issueYear; //Год издания public Book(String author, String name, Integer issueYear) { this.author = author; this.name = name; this.issueYear = issueYear; } public String getAuthor() { return author; } public String getName() { return name; } public Integer getIssueYear() { return issueYear; } @Override public String toString() { return "Book{" + "author='" + author + '\'' + ", name='" + name + '\'' + ", issueYear=" + issueYear + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Book book = (Book) o; return author.equals(book.author) && name.equals(book.name) && issueYear.equals(book.issueYear); } @Override public int hashCode() { return Objects.hash(author, name, issueYear); } }Класс для книг - Bookimport java.util.ArrayList; import java.util.List; public class Reader { private String fio; //Ф. И. О. private String email; //электронный адрес private boolean subscriber; //флаг согласия на рассылку private List<Book> books; //взятые книги public Reader(String fio, String email, boolean subscriber) { this.fio = fio; this.email = email; this.subscriber = subscriber; this.books = new ArrayList<>(); } public boolean isSubscriber() { return subscriber; } public String getFio() { return fio; } public String getEmail() { return email; } public List<Book> getBooks() { return books; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Reader reader = (Reader) o; return fio.equals(reader.fio); } @Override public int hashCode() { return Objects.hash(fio); } } Класс для читателей - Readerpublic class EmailAddress { private String email; //электронный адрес private String someData; /*доп. информация для формирования письма. В примерах не используем - добавили, чтобы оправдать существование отдельного класса :)*/ public EmailAddress(String email) { this.email = email; } public String getEmail() { return email; } public String getSomeData() { return someData; } public void setSomeData(String someData) { this.someData = someData; } }Класс для адреса рассылкиimport java.util.ArrayList; import java.util.List; public class Library { private List<Book> books; private List<Reader> readers; public Library() { init(); } private void init() { books = new ArrayList<>(); books.add(new Book("Оруэлл", "1984", 2021)); //и так далее для других книг readers = new ArrayList<>(); readers.add(new Reader("Иванов Иван Иванович", "ivanov.email@test.ru", true)); //и так далее для других читателей readers.get(0).getBooks().add(books.get(1)); //и так далее для других читателей и взятых книг } public List<Book> getBooks() { return books; } public List<Reader> getReaders() { return readers; } }Класс библиотеки (Library) с примером заполнения данных<p>Этот метод используется для сортировки элементов стрима. По умолчанию применяется сортировка по возрастанию (с числами всё понятно, а вот заглавные и строчные буквы рассматриваются отдельно).</p>
26
<p><strong>Примечание.</strong>Метод подходит только для сортировки объектов, которые реализуют интерфейс Comparable.</p>
26
<p><strong>Примечание.</strong>Метод подходит только для сортировки объектов, которые реализуют интерфейс Comparable.</p>
27
<p>Если же классы наших объектов не реализуют этот интерфейс или нужна иная логика сортировки, то можно передать в качестве аргумента свой алгоритм сравнения элементов.</p>
27
<p>Если же классы наших объектов не реализуют этот интерфейс или нужна иная логика сортировки, то можно передать в качестве аргумента свой алгоритм сравнения элементов.</p>
28
<p>В результате работы метода получается новый стрим.</p>
28
<p>В результате работы метода получается новый стрим.</p>
29
<p>Получить список всех книг библиотеки, отсортированных по году издания.</p>
29
<p>Получить список всех книг библиотеки, отсортированных по году издания.</p>
30
<p>У интерфейса List есть метод для сортировки - sort(). В него тоже можно передать алгоритм сравнения. До появления лямбд для этого создавали свои классы, реализующие интерфейс Comparator, или анонимные классы:</p>
30
<p>У интерфейса List есть метод для сортировки - sort(). В него тоже можно передать алгоритм сравнения. До появления лямбд для этого создавали свои классы, реализующие интерфейс Comparator, или анонимные классы:</p>
31
public static List<Book> doWithoutLambda(List<Book> books) { books.sort(new Comparator<Book>() { @Override public int compare(Book o1, Book o2) { return o1.getIssueYear().compareTo(o2.getIssueYear()); } }); return books; }<p>Метод sort() не возвращает результат, он преобразует исходную<a>коллекцию</a>. Поэтому в нашем примере пришлось вынести сортировку в отдельный метод, чтобы не менять текущий порядок книг.</p>
31
public static List<Book> doWithoutLambda(List<Book> books) { books.sort(new Comparator<Book>() { @Override public int compare(Book o1, Book o2) { return o1.getIssueYear().compareTo(o2.getIssueYear()); } }); return books; }<p>Метод sort() не возвращает результат, он преобразует исходную<a>коллекцию</a>. Поэтому в нашем примере пришлось вынести сортировку в отдельный метод, чтобы не менять текущий порядок книг.</p>
32
Library library = new Library(); List list = library.getBooks().stream() .sorted(Comparator.comparing(Book::getIssueYear)) .collect(Collectors.toList());<p>Для передачи алгоритма сравнения элементов в метод sorted() используется лямбда-выражение Comparator.comparing(Book: :getIssueYear).</p>
32
Library library = new Library(); List list = library.getBooks().stream() .sorted(Comparator.comparing(Book::getIssueYear)) .collect(Collectors.toList());<p>Для передачи алгоритма сравнения элементов в метод sorted() используется лямбда-выражение Comparator.comparing(Book: :getIssueYear).</p>
33
<p>Оно равносильно анонимному классу в примере выше: означает, что книги сравниваются по году издания.</p>
33
<p>Оно равносильно анонимному классу в примере выше: означает, что книги сравниваются по году издания.</p>
34
<p>Метод collect(Collectors.toList()) замыкает стрим в список (List).</p>
34
<p>Метод collect(Collectors.toList()) замыкает стрим в список (List).</p>
35
<p>Создавать отдельный метод для сортировки не пришлось. И в целом код выглядит компактнее.</p>
35
<p>Создавать отдельный метод для сортировки не пришлось. И в целом код выглядит компактнее.</p>
36
<p>Метод используется для преобразования объектов. Это может быть простое извлечение значения одного поля или создание объектов другого типа по данным объекта-источника.</p>
36
<p>Метод используется для преобразования объектов. Это может быть простое извлечение значения одного поля или создание объектов другого типа по данным объекта-источника.</p>
37
<p>Требуется создать список рассылки (объекты типа EmailAddress) из адресов всех читателей библиотеки. При этом флаг согласия на рассылку учитывать не будем: библиотека закрывается, так что хотим оповестить всех.</p>
37
<p>Требуется создать список рассылки (объекты типа EmailAddress) из адресов всех читателей библиотеки. При этом флаг согласия на рассылку учитывать не будем: библиотека закрывается, так что хотим оповестить всех.</p>
38
List<EmailAddress> addresses = new ArrayList<>(); for (Reader reader : library.getReaders()) { addresses.add(new EmailAddress(reader.getEmail())); }<p>Здесь мы не только используем цикл, но и меняем экземпляр нового списка в ходе итерирования. Если понадобятся хоть какие-то условия отбора, конструкция ещё больше усложнится.</p>
38
List<EmailAddress> addresses = new ArrayList<>(); for (Reader reader : library.getReaders()) { addresses.add(new EmailAddress(reader.getEmail())); }<p>Здесь мы не только используем цикл, но и меняем экземпляр нового списка в ходе итерирования. Если понадобятся хоть какие-то условия отбора, конструкция ещё больше усложнится.</p>
39
List<EmailAddress> addresses = library.getReaders().stream() .map(Reader::getEmail) .map(EmailAddress::new) .collect(Collectors.toList());<p>При первом использовании map() получаем из списка читателей список электронных адресов. На следующем шаге создаем экземпляры нужного нам класса EmailAddress, а далее собираем полученные адреса в список.</p>
39
List<EmailAddress> addresses = library.getReaders().stream() .map(Reader::getEmail) .map(EmailAddress::new) .collect(Collectors.toList());<p>При первом использовании map() получаем из списка читателей список электронных адресов. На следующем шаге создаем экземпляры нужного нам класса EmailAddress, а далее собираем полученные адреса в список.</p>
40
<p>Метод фильтрует стрим согласно переданному в метод условию-предикату. Позволяет записать условие в одну строчку без громоздких конструкций if-else.</p>
40
<p>Метод фильтрует стрим согласно переданному в метод условию-предикату. Позволяет записать условие в одну строчку без громоздких конструкций if-else.</p>
41
<p>Снова нужно получить список рассылки. Но на этот раз включаем в него только адреса читателей, которые согласились на рассылку. Дополнительно нужно проверить, что читатель взял из библиотеки больше одной книги.</p>
41
<p>Снова нужно получить список рассылки. Но на этот раз включаем в него только адреса читателей, которые согласились на рассылку. Дополнительно нужно проверить, что читатель взял из библиотеки больше одной книги.</p>
42
List<EmailAddress> addresses = new ArrayList<>(); for (Reader reader : library.getReaders()){ if (reader.getBooks().size() > 1 && reader.isSubscriber()) addresses.add(new EmailAddress(reader.getEmail())); }<p>Как видим, к циклу добавилась ещё пара условий. Проверяться они будут для каждого читателя.</p>
42
List<EmailAddress> addresses = new ArrayList<>(); for (Reader reader : library.getReaders()){ if (reader.getBooks().size() > 1 && reader.isSubscriber()) addresses.add(new EmailAddress(reader.getEmail())); }<p>Как видим, к циклу добавилась ещё пара условий. Проверяться они будут для каждого читателя.</p>
43
List<EmailAddress> addresses = library.getReaders().stream() .filter(Reader::isSubscriber) .filter(reader -> reader.getBooks().size() > 1) .map(Reader::getEmail).map(EmailAddress::new) .collect(Collectors.toList());<p>На первом шаге (первое использование filter()) сокращаем число читателей: работаем со списком тех, кто дал согласие на рассылку.</p>
43
List<EmailAddress> addresses = library.getReaders().stream() .filter(Reader::isSubscriber) .filter(reader -> reader.getBooks().size() > 1) .map(Reader::getEmail).map(EmailAddress::new) .collect(Collectors.toList());<p>На первом шаге (первое использование filter()) сокращаем число читателей: работаем со списком тех, кто дал согласие на рассылку.</p>
44
<p>На втором шаге из этого ограниченного числа читателей выбираем тех, кто взял более одной книги.</p>
44
<p>На втором шаге из этого ограниченного числа читателей выбираем тех, кто взял более одной книги.</p>
45
<p>Далее уже знакомыми map() и collect() получаем email-адреса, преобразуем их к нужному типу и собираем в список.</p>
45
<p>Далее уже знакомыми map() и collect() получаем email-адреса, преобразуем их к нужному типу и собираем в список.</p>
46
<p>Результат работы flatMap() получается в два действия, на которые намекает само название метода. Эти слова и эти операции:</p>
46
<p>Результат работы flatMap() получается в два действия, на которые намекает само название метода. Эти слова и эти операции:</p>
47
<ul><li><strong>map</strong>(мы уже знаем, что это преобразование);</li>
47
<ul><li><strong>map</strong>(мы уже знаем, что это преобразование);</li>
48
<li>и <strong>flat</strong> - дословно "плоский".</li>
48
<li>и <strong>flat</strong> - дословно "плоский".</li>
49
</ul><p>Если применить обычный map() к стриму из списков List<AnyType>, то на выходе получим стрим из списков списков - List<List<NewType>>.</p>
49
</ul><p>Если применить обычный map() к стриму из списков List<AnyType>, то на выходе получим стрим из списков списков - List<List<NewType>>.</p>
50
<p>flatMap() позволяет получить "плоский" одномерный список - List<NewType>, в который будут последовательно добавлены преобразованные значения из всех списков, полученных после применения map().</p>
50
<p>flatMap() позволяет получить "плоский" одномерный список - List<NewType>, в который будут последовательно добавлены преобразованные значения из всех списков, полученных после применения map().</p>
51
map vs flatMap<p>А далее о случае, когда эта операция бывает полезной.</p>
51
map vs flatMap<p>А далее о случае, когда эта операция бывает полезной.</p>
52
<p>Получить список всех книг, взятых читателями. Список не должен содержать дубликатов (книг одного автора, с одинаковым названием и годом издания).</p>
52
<p>Получить список всех книг, взятых читателями. Список не должен содержать дубликатов (книг одного автора, с одинаковым названием и годом издания).</p>
53
Set<Book> result = new LinkedHashSet<>(); for (Reader reader : library.getReaders()) { result.addAll(reader.getBooks()); } return new ArrayList<>(result);<p>Чтобы получить список уникальных книг, мы создали Set (множество), последовательно прошлись по всем читателям и добавили их книги в это множество. Только после этого преобразовали множество в список (ArrayList).</p>
53
Set<Book> result = new LinkedHashSet<>(); for (Reader reader : library.getReaders()) { result.addAll(reader.getBooks()); } return new ArrayList<>(result);<p>Чтобы получить список уникальных книг, мы создали Set (множество), последовательно прошлись по всем читателям и добавили их книги в это множество. Только после этого преобразовали множество в список (ArrayList).</p>
54
List<Book> bookList = library.getReaders().stream() .flatMap(reader -> reader.getBooks().stream()) .distinct() .collect(Collectors.toList());<p>После применения flatMap() уже получаем стрим, состоящий из всех книг всех читателей, а distinct() отвечает за то, чтобы в этом стриме остались только уникальные значения.</p>
54
List<Book> bookList = library.getReaders().stream() .flatMap(reader -> reader.getBooks().stream()) .distinct() .collect(Collectors.toList());<p>После применения flatMap() уже получаем стрим, состоящий из всех книг всех читателей, а distinct() отвечает за то, чтобы в этом стриме остались только уникальные значения.</p>
55
<p>Вот так - без дополнительных полей и циклов.</p>
55
<p>Вот так - без дополнительных полей и циклов.</p>
56
<p>Простой метод, который принимает на вход условие-предикат и возвращает флаг:</p>
56
<p>Простой метод, который принимает на вход условие-предикат и возвращает флаг:</p>
57
<ul><li><strong>true</strong>, если в стриме есть объект, который удовлетворяет условию;</li>
57
<ul><li><strong>true</strong>, если в стриме есть объект, который удовлетворяет условию;</li>
58
<li><strong>false</strong> - если такого объекта там нет.</li>
58
<li><strong>false</strong> - если такого объекта там нет.</li>
59
</ul><p>Проверить, взял ли кто-то из читателей библиотеки какие-нибудь книги Оруэлла.</p>
59
</ul><p>Проверить, взял ли кто-то из читателей библиотеки какие-нибудь книги Оруэлла.</p>
60
boolean result = false; for (Reader reader : library.getReaders()){ for (Book book :reader.getBooks()){ if ("Оруэлл".equals(book.getAuthor())){ result = true; break; } } } return result;<p>Организуем два (!) вложенных цикла и вводим дополнительную переменную для хранения промежуточного результата. Цикл прерывается при первой встрече с Оруэллом.</p>
60
boolean result = false; for (Reader reader : library.getReaders()){ for (Book book :reader.getBooks()){ if ("Оруэлл".equals(book.getAuthor())){ result = true; break; } } } return result;<p>Организуем два (!) вложенных цикла и вводим дополнительную переменную для хранения промежуточного результата. Цикл прерывается при первой встрече с Оруэллом.</p>
61
boolean match = library.getReaders().stream() .flatMap(reader -> reader.getBooks().stream()) .anyMatch(book -> "Оруэлл".equals(book.getAuthor()));<p>С помощью<strong>flatMap()</strong>получаем стрим из всех взятых книг, а <strong>anyMatch()</strong>определяет, есть ли среди авторов Оруэлл.</p>
61
boolean match = library.getReaders().stream() .flatMap(reader -> reader.getBooks().stream()) .anyMatch(book -> "Оруэлл".equals(book.getAuthor()));<p>С помощью<strong>flatMap()</strong>получаем стрим из всех взятых книг, а <strong>anyMatch()</strong>определяет, есть ли среди авторов Оруэлл.</p>
62
<p>Метод reduce() берёт стрим и редуцирует (сокращает) его до одного значения. Для этого в метод передаются начальное значение (необязательный параметр) и функция-аккумулятор с двумя параметрами.</p>
62
<p>Метод reduce() берёт стрим и редуцирует (сокращает) его до одного значения. Для этого в метод передаются начальное значение (необязательный параметр) и функция-аккумулятор с двумя параметрами.</p>
63
<p>Сначала эта функция применяется к начальному значению и первому элементу стрима, затем к полученному на этом шаге результату и следующему элементу стрима - и так до последнего элемента стрима.</p>
63
<p>Сначала эта функция применяется к начальному значению и первому элементу стрима, затем к полученному на этом шаге результату и следующему элементу стрима - и так до последнего элемента стрима.</p>
64
<p>Есть и более сложные варианты редукции, когда нужен третий параметр - функция комбинирования. Она полезна при распараллеливании задач или несовпадении типов аргументов функции-аккумулятора и результата этой функции.</p>
64
<p>Есть и более сложные варианты редукции, когда нужен третий параметр - функция комбинирования. Она полезна при распараллеливании задач или несовпадении типов аргументов функции-аккумулятора и результата этой функции.</p>
65
Вычисление максимального значения с помощью reduce ()<p>Узнать наибольшее число книг, которое сейчас на руках у читателя.</p>
65
Вычисление максимального значения с помощью reduce ()<p>Узнать наибольшее число книг, которое сейчас на руках у читателя.</p>
66
int max = 0; for (Reader reader : library.getReaders()){ if (reader.getBooks().size() > max) max = reader.getBooks().size(); } return max;<p>На старте принимаем за максимум наименьшее возможное число книг у каждого читателя, то есть 0. Потом перебираем всех читателей, смотрим, сколько у кого книг, и при необходимости обновляем максимум.</p>
66
int max = 0; for (Reader reader : library.getReaders()){ if (reader.getBooks().size() > max) max = reader.getBooks().size(); } return max;<p>На старте принимаем за максимум наименьшее возможное число книг у каждого читателя, то есть 0. Потом перебираем всех читателей, смотрим, сколько у кого книг, и при необходимости обновляем максимум.</p>
67
Integer reduce = library.getReaders().stream() .map(reader -> reader.getBooks().size()) .reduce(0, (max, size) -> size > max ? size : max);<p>На первом шаге с помощью map() соотносим с каждым читателем число взятых им книг, а затем с помощью reduce() находим максимальный элемент в этом новом стриме.</p>
67
Integer reduce = library.getReaders().stream() .map(reader -> reader.getBooks().size()) .reduce(0, (max, size) -> size > max ? size : max);<p>На первом шаге с помощью map() соотносим с каждым читателем число взятых им книг, а затем с помощью reduce() находим максимальный элемент в этом новом стриме.</p>
68
<p><strong>Это важно.</strong>При каждом вызове функции-аккумулятора создаётся новый объект. Если вы хотите, чтобы на выходе reduce() оказался сложный объект - например, коллекция, и ходите в функции-аккумуляторе добавлять в неё значения, то на каждом шаге будет создаваться новая коллекция.</p>
68
<p><strong>Это важно.</strong>При каждом вызове функции-аккумулятора создаётся новый объект. Если вы хотите, чтобы на выходе reduce() оказался сложный объект - например, коллекция, и ходите в функции-аккумуляторе добавлять в неё значения, то на каждом шаге будет создаваться новая коллекция.</p>
69
<p>Это плохо сказывается на производительности. В таких случаях лучше использовать collect().</p>
69
<p>Это плохо сказывается на производительности. В таких случаях лучше использовать collect().</p>
70
<p>Методы groupingBy() и mapping() вовсе не обязательно применять вместе. Первый позволяет разбить стрим на группы по заданному признаку. Если эти группы нужны в виде списков, то второй метод не понадобится.</p>
70
<p>Методы groupingBy() и mapping() вовсе не обязательно применять вместе. Первый позволяет разбить стрим на группы по заданному признаку. Если эти группы нужны в виде списков, то второй метод не понадобится.</p>
71
<p>mapping() выручит, если полученные группы нужно хитрым (или не очень) образом преобразовать (например, сгруппировать по другим признакам).</p>
71
<p>mapping() выручит, если полученные группы нужно хитрым (или не очень) образом преобразовать (например, сгруппировать по другим признакам).</p>
72
<p>Вернёмся к нашим баранам email-рассылкам. Теперь нужно не просто отправить письма всем, кто согласился на рассылку, - будем рассылать разные тексты двум группам:</p>
72
<p>Вернёмся к нашим баранам email-рассылкам. Теперь нужно не просто отправить письма всем, кто согласился на рассылку, - будем рассылать разные тексты двум группам:</p>
73
<ul><li><strong>тем, у кого взято меньше двух книг</strong>, просто расскажем о новинках библиотеки;</li>
73
<ul><li><strong>тем, у кого взято меньше двух книг</strong>, просто расскажем о новинках библиотеки;</li>
74
<li><strong>тем, у кого две книги и больше</strong>, напомним о том, что их нужно вернуть в срок.</li>
74
<li><strong>тем, у кого две книги и больше</strong>, напомним о том, что их нужно вернуть в срок.</li>
75
</ul><p>То есть надо написать метод, который вернёт два списка адресов (типа EmailAddress): с пометкой OK - если книг не больше двух, или TOO_MUCH - если их две и больше. Порядок групп не важен.</p>
75
</ul><p>То есть надо написать метод, который вернёт два списка адресов (типа EmailAddress): с пометкой OK - если книг не больше двух, или TOO_MUCH - если их две и больше. Порядок групп не важен.</p>
76
<p>Приготовьтесь, сейчас будет страшно.</p>
76
<p>Приготовьтесь, сейчас будет страшно.</p>
77
Map<String, List<EmailAddress>> result = new HashMap<>(); for (Reader reader : library.getReaders()) { if (reader.isSubscriber()) { if (reader.getBooks().size() > 2) { if (!result.containsKey("TOO_MUCH")) { result.put("TOO_MUCH", new ArrayList<>()); } result.get("TOO_MUCH").add(new EmailAddress(reader.getEmail())); } else { if (!result.containsKey("OK")) { result.put("OK", new ArrayList<>()); } result.get("OK").add(new EmailAddress(reader.getEmail())); } } } return result;<p>Цикл и три уровня ветвлений. И это всего для двух групп!</p>
77
Map<String, List<EmailAddress>> result = new HashMap<>(); for (Reader reader : library.getReaders()) { if (reader.isSubscriber()) { if (reader.getBooks().size() > 2) { if (!result.containsKey("TOO_MUCH")) { result.put("TOO_MUCH", new ArrayList<>()); } result.get("TOO_MUCH").add(new EmailAddress(reader.getEmail())); } else { if (!result.containsKey("OK")) { result.put("OK", new ArrayList<>()); } result.get("OK").add(new EmailAddress(reader.getEmail())); } } } return result;<p>Цикл и три уровня ветвлений. И это всего для двух групп!</p>
78
Map<String, List<EmailAddress>> map = library.getReaders().stream() .filter(Reader::isSubscriber) .collect(groupingBy(r -> r.getBooks().size() > 2 ? "TOO_MUCH" : "OK", mapping(r -> new EmailAddress(r.getEmail()), Collectors.toList())));<p>На первом шаге фильтруем читателей: оставляем только тех, кто согласился на рассылку. Дальше настраиваем параметры метода collect():</p>
78
Map<String, List<EmailAddress>> map = library.getReaders().stream() .filter(Reader::isSubscriber) .collect(groupingBy(r -> r.getBooks().size() > 2 ? "TOO_MUCH" : "OK", mapping(r -> new EmailAddress(r.getEmail()), Collectors.toList())));<p>На первом шаге фильтруем читателей: оставляем только тех, кто согласился на рассылку. Дальше настраиваем параметры метода collect():</p>
79
<ul><li>задаём группировку - нужно разбить стрим на две группы по числу книг: "TOO_MUCH" или "OK";</li>
79
<ul><li>задаём группировку - нужно разбить стрим на две группы по числу книг: "TOO_MUCH" или "OK";</li>
80
<li>в каждой группе берём email-адреса читателей (new EmailAddress (r.getEmail())) и собираем их в списки (Collectors.toList()).</li>
80
<li>в каждой группе берём email-адреса читателей (new EmailAddress (r.getEmail())) и собираем их в списки (Collectors.toList()).</li>
81
</ul><p>1. Если нужны не адреса, а просто списки читателей в каждой группе:</p>
81
</ul><p>1. Если нужны не адреса, а просто списки читателей в каждой группе:</p>
82
Map<String, List<Reader>> readerstMap = library.getReaders().stream() .filter(Reader::isSubscriber) .collect(groupingBy(r -> r.getBooks().size() > 2 ? "TOO_MUCH" : "OK"));<p>2. Если для каждой группы нужны Ф. И. О. читателей из этой группы, перечисленные через запятую. И ещё каждый такой список Ф. И. О. нужно обернуть фигурными скобками.</p>
82
Map<String, List<Reader>> readerstMap = library.getReaders().stream() .filter(Reader::isSubscriber) .collect(groupingBy(r -> r.getBooks().size() > 2 ? "TOO_MUCH" : "OK"));<p>2. Если для каждой группы нужны Ф. И. О. читателей из этой группы, перечисленные через запятую. И ещё каждый такой список Ф. И. О. нужно обернуть фигурными скобками.</p>
83
<p><strong>Например:</strong></p>
83
<p><strong>Например:</strong></p>
84
<p>TOO_MUCH {Иванов Иван Иванович, Васильев Василий Васильевич}</p>
84
<p>TOO_MUCH {Иванов Иван Иванович, Васильев Василий Васильевич}</p>
85
<p>OK {Семёнов Семён Семёнович}</p>
85
<p>OK {Семёнов Семён Семёнович}</p>
86
Map<String, String> readersFIOMap = library.getReaders().stream() .filter(Reader::isSubscriber) .collect(groupingBy(r -> r.getBooks().size() > 2 ? "TOO_MUCH" : "OK", mapping(Reader::getFio, joining(", ", "{", "}"))));<p>Ещё больше о Stream API вы узнаете из <a>официальной документации</a>и на нашем курсе "<a>Профессия Java-разработчик</a>".</p>
86
Map<String, String> readersFIOMap = library.getReaders().stream() .filter(Reader::isSubscriber) .collect(groupingBy(r -> r.getBooks().size() > 2 ? "TOO_MUCH" : "OK", mapping(Reader::getFio, joining(", ", "{", "}"))));<p>Ещё больше о Stream API вы узнаете из <a>официальной документации</a>и на нашем курсе "<a>Профессия Java-разработчик</a>".</p>
87
<a>Научитесь: Профессия Java-разработчик + ИИ Узнать больше</a>
87
<a>Научитесь: Профессия Java-разработчик + ИИ Узнать больше</a>