Эта статья, как и все последующие и предыдущие – моя попытка структурировать полученные знания в процессе изучения Java. Здесь тезисно собрана вся основная информация по теме и те формулировки, которые показались мне наиболее удачными и понятными. Это мой конспект, если хотите.
Статья будет полезна тем, кто изучает или повторяет основы Java Core.
И тем, кто готовится к техническому интервью.
На источники, откуда черпалась информация, предоставлены ссылки в конце статьи.
Оглавление
Основные понятия
С выходом Java 8 появилась поддержка функционального программирования.
Стало возможным конструировать сложные цепочки операций над потоком данных.
Функциона́льное программи́рование — парадигма программирования, в которой процесс вычисления трактуется как вычисление значений функций в математическом понимании последних (в отличие от функций как подпрограмм в процедурном программировании).
Пакет java.util.stream содержит классы для поддержки операций с потоками элементов в функциональном стиле. Ключевой абстракцией, введенной в этом пакете, является Поток.
Stream API – по сути это поток данных и последовательные операции над ними.
Интерфейсы Stream, IntStream, LongStream и DoubleStream – это потоки объектов и примитивных типов int
, long
и double
.
public interface Stream<T> extends BaseStream<T,Stream<T>>
Интерфейс Stream представляет собой последовательность элементов, поддерживающих последовательные и параллельные агрегатные операции. К агрегатным операциям относят различные операции над выборкой, например, получение числа элементов, получение минимального, максимального и среднего значения в выборке, а также суммирование значений. Stream не предоставляет средств для прямого доступа к элементам или манипулирования ими. Вместо этого он описывает источник данных и вычислительные операции, которые будут выполняться над этими данными. Чтобы обеспечить способ работы с тремя наиболее часто используемыми типами примитивов – Создание потока примитивных типов Стримы примитивных типов данных имеют ряд уникальных методов, например диапазон и сумма всех элементов потока. Методы диапазона доступны только для IntStream и LongStream. В методе Поток элементов может быть получен разными способами, например: Stream из List Stream из Map Stream из массива, используя статический метод класса Arrays Stream из элементов, используя статические методы Классов-потоков Stream из строк буфера BufferedReader Stream из строк файла через статический метод класса Files Stream из случайных чисел Random Операции над потоком элементов бывают: Intermediate – Промежуточные Terminal – Конечные Промежуточные операции НЕ выполняются без терминальных. Операции над потоком могут выполняться как последовательно, так и параллельно. Данные операции удобно воспринимать как отложенные – они выполнятся когда их запустит конечная терминальная операция над стримом. Провести операцию над каждым элементом Преобразовать данные из одного типа в другой Отфильтровать элементы Удалить дублирующиеся элементы Сортировка и обратная сортировка элементов Лимит количества элементов Пропустить первые элементы Сопоставить поток с развернутым потоком Таким образом можно развернуть двухмерный массив Запускают всю цепь промежуточных операций и возвращают конечный результат, закрывают поток. Собрать элементы потока и преобразовать их к нужному типу Преобразовать поток в List Преобразовать поток в строку String Итерация по каждому элементу Узнать количество элементов стрима Найти минимальное и максимальное значение Comparator удобно задать с помощью лямбда-функции: Найти первый подходящий элемент Найти любой подходящий элемент Все элементы соответствуют условию Все элементы НЕ соответствуют условию Хотя бы один элемент соответствует условию Сумма элементов стрима Позволяет получить один результат из последовательности элементов, неоднократно применяя операцию комбинирования к элементам в последовательности. Участники операции сведения: Identity — элемент, который является начальным значением операции сокращения и результатом по умолчанию, если поток пуст. Accumulator — функция, которая принимает два параметра: частичный результат операции сведения и следующий элемент потока Combiner — функция, используемая для объединения частичного результата операции сокращения и типами реализации аккумулятора. Сумма элементов списка Сумма элементов в параллельном потоке Объединение списка строк в одну сроку Методы интерфейса Stream Это объект-контейнер, который может содержать или не содержать нулевое значение. Позволяет избавиться от проверки на Получить элемент Optional Метод В метод Класс Collectors содержит статические методы для сбора элементов в коллекцию, обобщения и группировки элементов в соответствии с различными критериями и т. п., которые возвращают готовые объекты коллекций. Преобразование потока в List Преобразование потока в Set Преобразование потока в Map Объединение элементов в строку String Сумма элементов потока Сгруппировать элементы по условию
Стрим примитивных типов
int
, long
и double
– библиотека java.util.stream включает три реализации стрима примитивов:public interface IntStream extends BaseStream<Integer,IntStream>
public interface LongStream extends BaseStream<Long,LongStream>
public interface DoubleStream extends BaseStream<Double,DoubleStream>
mapToInt(), mapToDouble(), mapToLong()
List<Integer> numbers = new ArrayList<>(); numbers.stream().mapToInt(value -> value); numbers.stream().mapToDouble(value -> value); numbers.stream().mapToLong(value -> value);
Существует два метода получения стрима из диапазона чисел:range(int startInclusive, int endExclusive)
rangeClosed(int startInclusive, int endInclusive)
range()
второй аргумент метода не входит в диапазон чисел, в то время как метод rangeClosed()
включает его в диапазон. Запустить Stream
List<String> list = new ArrayList<>(); list.stream(); list.parallelStream(); // параллельный поток
Map<String, String> map = new HashMap<>(); map.entrySet().stream(); map.values().stream();
String[] array = new String[10]; Arrays.stream(array);
Stream.of("a", "b", "c"); // поток из элементов Stream.of(array); // поток из элементов массива Stream.of(list); // поток из элементов списка List Stream.generate(Math::random); // генерация потока рандомных чисел Stream.concat(stream1, stream2); // объединяет два потока в один IntStream.range(1, 10); // поток диапазона чисел от 1 до 9 IntStream.rangeClosed(1, 10); // поток диапазона чисел от 1 до 10
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); bufferedReader.lines();
Path path = Path.of("/root/test.txt"); Files.lines(path);
Random random = new Random(); random.ints(); random.longs(); random.doubles();
Операции над Stream
Не меняют данные, а только задают логику их изменения.
С их помощью можно составить последовательную цепь операций над элементами.
Запускают всю цепь промежуточных операций, закрывают поток и возвращают модифицированные данные.
После обработки стрим нельзя повторно использовать.
Чтобы превратить обычный стрим в параллельный, нужно вызвать промежуточный оператор parallel()
Stream<String> stream = list.stream(); stream.parallel();
Промежуточные операции. Intermediate
peek()
Аналог forEach()
, только промежуточный (нетерминальный)
Если в метод peek()
передать функцию System.out::println
, тогда все объекты будут выводиться на экран в момент, когда они будут проходить через поток.Stream.of("a", "b", "c").peek(System.out::println);
map()
Можно передать функцию, которая преобразовывает один тип данных в другой.Stream.of(1, 2, 3).map((x) -> String.valueOf(x)); Stream.of(1, 2, 3).map(String::valueOf); // лямбда выражение Stream.of("1", "2", "3").map(Integer::parseInt);
filter()
Stream.of(1, 2, 3, 4, 5).filter(n -> n < 4); // [1, 2, 3]
distinct()
Stream.of(1, 2, 3, 2, 4, 2, 5).distinct(); // [1, 2, 3, 4, 5]
sorted()
Stream.of(4, 2, 3, 5, 1).sorted(); // [1, 2, 3, 4, 5] Stream.of(4, 2, 3, 5, 1).sorted(Comparator.reverseOrder())
limit()
Stream.of(1, 2, 3, 4, 5, 6).limit(3); // [1, 2, 3]
skip()
Stream.of(1, 2, 3, 4, 5).skip(2); // [3, 4, 5]
flatMap()
Возвращает поток, состоящий из результатов замены каждого элемента этого потока содержимым сопоставленного потока, полученного путем применения предоставленной функции сопоставления к каждому элементу.List<String> petNames = person.stream() .flatMap(person -> person.getPetName().stream()) .collect(Collectors.toList()); System.out.println(petNames); // [pet1, pet2, pet3, pet4, pet5]
Integer[][] array2d = new Integer[][] { {1, 2, 3}, {4, 5} }; Arrays.stream(array2d).flatMap(Arrays::stream); // [1, 2, 3, 4, 5]
Конечные операции. Terminal
collect()
В аргумент метода нужно передать объект Collector.Collectors.toList()
List<String> collect = Stream.of("a", "b", "c").collect(Collectors.toList());
Collectors.joining()
String collect = Stream.of("a", "b", "c").collect(Collectors.joining());
forEach()
Stream.of("a", "b", "c").forEach(System.out::println);
count()
Stream.of("a", "b", "c").count();
min()
и max()
Сравнение происходит с помощью объекта Comparator.
Возвращают объект класса Optional – объект-контейнер, который может хранить null
.
Метод get()
– возвращает значение, которое хранит объект Optional.Optional<Integer> max = Stream.of(4, 2, 3, 5, 1) .max(Comparator.naturalOrder()); Integer maximum = max.get(); Integer minimum = Stream.of(4, 2, 3, 5, 1) .min(Comparator.naturalOrder()) .get();
Stream.of("a", "bb", "ccc") .min((s1, s2) -> s1.length() - s2.length()) .get(); Stream.of("a", "bb", "ccc") .max(Comparator.comparingInt(String::length)) .get();
findFirst()
Возвращает первый подходящий элемент из стрима и завершается.
Возвращают объект класса Optional.Stream.of(1, 2, 3, 4, 5) .filter(e -> e % 2 == 0) .findFirst() .get();
findAny()
Возвращает любой подходящий элемент из стрима и завершается.
Аналог метода findFirst()
для потоков, которые обрабатываются параллельно.
Найденный элемент не обязательно будет первый по порядку в потоке.Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .filter(e -> e % 2 == 0) .parallel() .findAny() .get();
allMatch()
Stream.of(1, 2, 3, 4, 5).allMatch(e -> e > 0); // true
noneMatch()
Stream.of(1, 2, 3, 4, 5).noneMatch(e -> e > 0); // false
anyMatch()
Stream.of(1, 2, 3, 4, 5).anyMatch(e -> e > 4); // true
sum()
Это метод классов-стримов примитивных типов данных:
IntStream, LongStream и DoubleStreamList<Integer> integers = new ArrayList<>(); integers.stream() .mapToInt(i -> i) .sum();
Операция сведения Stream.reduce()
Если используем последовательные потоки, типы аргументов аккумулятора и типы его реализации не совпадают – нужно использовать Combiner.List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); int sum = numbers.stream() .reduce(0, Integer::sum); int sum2 = numbers.stream() .reduce(0, (subtotal, element) -> subtotal + element);
В таких случаях нужно использовать функцию для объединения результатов подпотоков в один – это роль Combiner Комбинатора.
В приведенном примере эту роль выполняет метод Integer::sum
int sum = numbers .parallelStream() .reduce(0, (a, b) -> a + b, Integer::sum); int sum2 = ages .parallelStream() .reduce(0, Integer::sum, Integer::sum);
List<String> letters = Arrays.asList("a", "b", "c", "d", "e"); String result = letters.stream() .reduce("", String::concat); String result2 = letters.stream() .reduce("", (partialString, element) -> partialString + element);
Класс Optional
findAny()
, findFirst()
, max()
, min()
и reduce()
возвращают объект класса Optionalpublic final class Optional<T> extends Object
Ссылка на объект класса Optional может быть null
Если значение присутствует, метод isPresent()
вернет true
null
Без этого класса приходилось писать проверку на NullPointerException.
Благодарю этому один объект Optional можно сравнить с другим объектом Optional через метод equals()
, даже если они хранят в себе ссылки на null
.get()
Метод класса Optoinal – возвращает значение, если оно присутствует, в противном случае бросит NoSuchElementException .Stream.of("1", "22", "333") .max(Comparator.comparingInt(String::length)) .get();
Класс Collectors
collect()
интерфейса Stream собирает данные в необходимую структуру данных, например в коллекции — List<T>
, Set<T>
, Map<T, R>
public interface Collector<T,A,R>
collect()
в качестве параметра принимает объект типа Collector.
Статические методы класса Collectors возвращают такой объект класса Collector.public final class Collectors extends Object
toList()
list.stream().collect(Collectors.toList());
toSet()
list.stream().collect(Collectors.toSet());
toMap()
map.entrySet().stream() .map(e -> String.valueOf(e).split("=")) .collect(Collectors.toMap(e -> e[0], e -> e[1]));
joining()
Объединение потока коллекции List в одну строку через запятуюlist.stream().collect(Collectors.joining(", "));
summingInt(), summingDouble(), summingLong()
Например, сумма заработной платы сотрудниковList<Employee> employees = new ArrayList<>() employees.stream() .collect(Collectors.summingDouble(Employee::getSalary)));
groupingBy()
Группировка людей по странеList<Person> people = new ArrayList<>() people.stream() .collect(Collectors.groupingBy(Person::getCountry));
Список ссылок на источники информации
ссылка на оригинал статьи https://habr.com/ru/post/693666/
Добавить комментарий