Java Streams, добавленные в Java 8, стали очень популярными и представляют собой мощный способ обработки коллекций объектов. Поток — это последовательность объектов из источника, поддерживающая цепочку методов для получения желаемого результата.
Отладка потоков Java может быть сложной. В этом посте мы научимся отлаживать потоки, поскольку их элементы обрабатываются в цепочечных вызовах методов.
1. Почему потоки сложно отлаживать?
Иногда потоки Java 8 могут быть сложны в отладке. Это происходит, поскольку они требуют от нас вставки дополнительных точек останова и тщательного анализа каждого преобразования внутри потока.
Например, у нас есть класс Student:
public class Student {private String name;private String email;private Double grade;//getters, setters, constructor, toString method}
Мы можем составить список студентов:
List<Student> students = List.of(new Student("Alexandru","alex@gmail.com",5.6),new Student("Emmanuela","emma@yahoo.com",7.2),new Student("John","john@gmail.com",10.0),new Student("Andrew","andrew",6.2),new Student("Anna","anna@gmail.com",6.2));
Предположим, мы хотим получить всех студентов в алфавитном порядке, имеющих действительный адрес электронной почты и проходной балл. Поэтому мы используем потоковые операции API:
List<Student> newList = students.stream().filter(student -> student.getEmail().endsWith("@gmail.com")).filter(student -> student.getGrade() > 5.0).sorted(Comparator.comparing(Student::getName)).collect(Collectors.toList());
После запуска программы мы получаем только одного студента. Поэтому мы хотим отладить поток, чтобы увидеть, как он фильтрует студентов.
См. также: Руководство по Java Stream API
2. Отладка с использованием API peek()
Мы можем отладить поток, используя метод peek() для регистрации информации о данных на каждом шаге. Метод peek() возвращает поток, состоящий из элементов исходного потока, и выполняет действие, запрошенное клиентом каждого элемента.
List<Student> newList = students.stream().filter(student -> student.getEmail().endsWith("@gmail.com")).peek(student -> System.out.println("Filtered 1 value:" + student)).filter(student -> student.getGrade() > 5.0).peek(student -> System.out.println("Filtered 2 value:" + student)).sorted(Comparator.comparing(Student::getName)).collect(Collectors.toList());
Обратите внимание на вывод программы. Мы видим, что метод peek() четко выводит элементы потока в конвейере после каждого вызова метода filter(). Мы видим, что 3 студента прошли первый фильтр и только один прошел второй.
Filtered 1 value:Student{name="Alexandru", email="alex@gmail.com", grade=2.6}Filtered 1 value:Student{name="John", email="john@gmail.com", grade=10.0}Filtered 2 value:Student{name="John", email="john@gmail.com", grade=10.0}Filtered 1 value:Student{name="Anna", email="anna@gmail.com", grade=4.2}
3. Отладка с помощью IntelliJ Stream Debugger
IntelliJ Stream Debugger — это скрытый бриллиант, и он очень прост в использовании. Он позволяет визуализировать поток. Давайте используем это в нашем примере.
На первом этапе мы установим точку останова в потоке.

Теперь запустим программу в режиме отладки. Программа будет приостановлена, когда поток будет создан.

А теперь нажмем кнопку «Trace Current Stream Chain». Откроется новая вкладка, и здесь мы сможем увидеть, как поток фильтрует данные.

4. Заключение
Потоки могут показаться сложными для отладки. Но здесь на помощь приходят специальные методы API потоков, а также специальные инструменты IDE, которые мы используем ежедневно.