Stream API
Что такое Stream?
В Java Stream (поток) представляет собой последовательность элементов данных, которую можно обрабатывать в функциональном стиле. Stream API был введен в Java 8 и предоставляет удобные и мощные средства для работы с коллекциями данных, выполнения различных операций фильтрации, сортировки, преобразования и агрегации элементов.
Java Stream
:
-
Ленивые вычисления: Операции над потоками выполняются лениво (lazy), что означает, что элементы обрабатываются только по мере необходимости. Это позволяет оптимизировать работу с данными, избегая лишних вычислений.
-
Функциональный стиль: Операции над потоками часто выполняются с использованием лямбда-выражений, что позволяет писать компактный и выразительный код.
-
Операции над потоками: Stream API включает в себя разнообразные операции:
-
Промежуточные операции (
intermediate operations), такие какfilter,map,sorted,distinct, которые выполняются над элементами потока и возвращают новый поток. -
Терминальные операции (
terminal operations), например,forEach,collect,reduce,count, которые завершают обработку потока и возвращают результат.
-
-
Пример использования:
import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<String> fruits = Arrays.asList("Apple", "Orange", "Banana", "Grapes", "Mango"); // Пример использования Stream API long count = fruits.stream() .filter(fruit -> fruit.startsWith("A")) .sorted() .count(); System.out.println("Number of fruits starting with 'A': " + count); } }В приведенном примере создается поток (
Stream) из списка фруктов, фильтруются только те, которые начинаются с буквы “A”, затем сортируются и подсчитывается их количество.
Что такое Optional?
Optional в Java представляет собой контейнерный объект, который
может содержать или не содержать некоторое значение. Основная идея
Optional заключается в том, чтобы явно обозначить, что значение
может отсутствовать, и предоставить удобные методы для работы с этим возможным
отсутствующим значением без необходимости в явной проверке на null.
Optional
:
-
Может содержать или не содержать значение:
Optionalможет быть пустым (empty) или содержать конкретное значение (ненулевое). -
Предотвращение NullPointerException: Использование
Optionalпомогает избежатьNullPointerException, так как методыOptionalпредоставляют безопасные операции для работы с возможно отсутствующими значениями. -
Методы для работы с значениями:
-
isPresent(): Проверяет, содержит лиOptionalзначение. -
ifPresent(Consumer<? super T> consumer): Выполняет заданное действие, если значение присутствует. -
orElse(T other): Возвращает значение, если оно присутствует, иначе возвращает заданное значение по умолчанию. -
orElseGet(Supplier<? extends T> supplier): Возвращает значение, если оно присутствует, иначе возвращает значение, полученное от заданного поставщика. -
orElseThrow(Supplier<? extends X> exceptionSupplier): Возвращает значение, если оно присутствует, иначе выбрасывает исключение, полученное от заданного поставщика.
-
-
Пример использования:
import java.util.Optional; public class Main { public static void main(String[] args) { Optional<String> optionalString = Optional.of("Hello"); // Проверка наличия значения и вывод его if (optionalString.isPresent()) { System.out.println("Value: " + optionalString.get()); } else { System.out.println("No value present"); } // Использование метода ifPresent для вывода значения optionalString.ifPresent(value -> System.out.println("Value: " + value)); // Получение значения или возврат "Default" по умолчанию String result = optionalString.orElse("Default"); System.out.println("Result: " + result); // Получение значения или вызов исключения, если значение отсутствует String value = optionalString.orElseThrow(() -> new RuntimeException("Value is absent")); System.out.println("Value: " + value); } }В приведенном примере создается
Optionalобъект с значением"Hello". После этого демонстрируются методыisPresent(),ifPresent(),orElse()иorElseThrow()для работы с этим объектомOptional.
Какие существуют способы создания стрима?
В Java существует несколько способов создания стрима:
-
Из коллекции: Стрим можно создать из коллекции с помощью метода
stream():List<String> list = Arrays.asList("apple", "banana", "cherry"); Stream<String> streamFromCollection = list.stream(); -
Из массива: Для создания стрима из массива используется статический метод
Stream.of():String[] array = {"apple", "banana", "cherry"}; Stream<String> streamFromArray = Stream.of(array); -
Из значений (
varargs): Можно создать стрим напрямую из значений с помощьюStream.of():Stream<String> streamOfValues = Stream.of("apple", "banana", "cherry"); -
Из файла: Files.lines() возвращает поток, который нужно закрыть, обычно через try-with-resources. Это освобождает открытый файл даже при исключении.
Path path = Paths.get("file.txt"); Stream<String> lines = Files.lines(path); -
С помощью метода
generateилиiterate: Для создания бесконечных стримов используются методыStream.generate()иStream.iterate():-
Stream.generate(Supplier<T> supplier): Генерирует элементы на основе заданного поставщика.Stream<Integer> infiniteStream = Stream.generate(() -> 1); -
Stream.iterate(T seed, UnaryOperator<T> f): Создает стрим, начиная с заданного начального значения и применяя функцию к предыдущему элементу для получения следующего.Stream<Integer> numbers = Stream.iterate(0, n -> n + 2);
-
-
Из регулярного выражения: Метод
Pattern.splitAsStream()позволяет создать стрим из разделенных элементов строки по заданному регулярному выражению.Pattern pattern = Pattern.compile(","); Stream<String> splitStream = pattern.splitAsStream("apple,banana,cherry");
Какова цель метода filter() в стримах?
Метод filter() в Java Stream API используется для фильтрации элементов стрима на основе заданного условия. Он позволяет выбирать только те элементы, которые соответствуют заданному предикату (условию).
filter()
состоит в том, чтобы:
-
Уменьшить объем данных, с которыми нужно работать в дальнейших операциях.
-
Оставить только те элементы стрима, которые удовлетворяют определенному критерию или условию.
Пример использования метода filter():
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Фильтрация четных чисел
Stream<Integer> evenNumbersStream = numbers.stream()
.filter(n -> n % 2 == 0);
// Сбор результата в список
List<Integer> evenNumbersList = evenNumbersStream.collect(Collectors.toList());
System.out.println(evenNumbersList); // Выведет: [2, 4, 6, 8, 10]
В этом примере filter() принимает предикат n -> n % 2 == 0, который проверяет, является ли число четным. Только числа, удовлетворяющие этому условию, проходят через фильтр и попадают в итоговый стрим evenNumbersStream.
В чем разница между Collection и Stream?
Основные различия между Collection и Stream в Java можно сформулировать следующим образом:
-
Назначение:
-
Collectionпредставляет собой структуру данных, которая хранит элементы и предоставляет методы для управления этими элементами (добавление, удаление, получение элементов и т.д.). -
Streamпредставляет собой последовательность элементов данных, которую можно обрабатывать в функциональном стиле. Он не хранит элементы, а предоставляет методы для выполнения операций над элементами данных (фильтрация, сортировка, преобразование и т.д.).
-
-
Изменяемость:
-
Collection может быть изменяемой или предоставлять неизменяемый доступ. Collections.unmodifiableList() возвращает представление, запрещающее изменения через него, но исходный список может изменяться отдельно.
-
Stream используют однократно. Промежуточные операции создают конвейер обработки и обычно не изменяют источник, но переданные функции могут иметь побочные эффекты. Модифицировать обычный источник во время обработки нельзя.
-
-
Вычисление:
-
Collectionхранит элементы в памяти, и все операции выполняются сразу после вызова метода. -
Streamиспользует ленивые вычисления: операции над стримом выполняются только при вызове терминальной операции. Это позволяет оптимизировать работу с данными и избегать лишних вычислений.
-
-
Использование:
-
Collectionподходит для хранения и манипуляций с наборами данных в памяти. -
Streamподходит для функциональной обработки данных без необходимости сохранять промежуточные результаты.
-
Collection
и
Stream
:
// Пример использования Collection
List<String> list = new ArrayList<>(Arrays.asList("apple", "banana", "cherry"));
list.add("orange"); // Добавление элемента в коллекцию
// Пример использования Stream
Stream<String> stream = list.stream();
stream.filter(s -> s.startsWith("a"))
.forEach(System.out::println); // Вывод элементов, начинающихся с "a"
В этом примере list — это коллекция, которая хранит элементы в памяти и поддерживает операции добавления и получения элементов.
stream — это стрим, созданный из коллекции, и он используется для
фильтрации элементов и вывода результатов без изменения исходной коллекции.
Для чего нужен метод collect() в стримах?
Метод collect() в Java Stream API используется для сбора элементов стрима в различные структуры данных, такие как коллекции (List, Set, Map), массивы или другие пользовательские контейнеры. Он предоставляет гибкую возможность окончательной обработки данных в стриме и преобразования их в желаемый формат.
collect()
:
-
Сбор в коллекции: Наиболее частое использование
collect()— это сбор элементов стрима в коллекцию. Например, в список (toList()), множество (toSet()), или карту (toMap()).List<String> list = Stream.of("apple", "banana", "cherry") .collect(Collectors.toList()); -
Группировка и агрегация: Метод
collect()позволяет группировать элементы и выполнять агрегирующие операции.Map<Integer, List<String>> map = Stream.of("apple", "banana", "cherry") .collect(Collectors.groupingBy(String::length)); -
Преобразование в массивы и другие структуры данных: Метод
collect()также может быть использован для преобразования элементов стрима в массивы, строки и другие пользовательские контейнеры.String[] array = Stream.of("apple", "banana", "cherry") .toArray(String[]::new); -
Собственные коллекции и контейнеры: Вы можете использовать
collect()для сбора элементов в собственные реализации коллекций или другие пользовательские контейнеры.