Способы создания stream java

Java 8 Stream API: шпаргалка для программиста

Обработка данных — стандартная задача при разработке. Раньше для этого приходилось использовать циклы или рекурсивные функции. С появлением в Java 8 Stream API процесс обработки данных значительно ускорился. Этот инструмент языка позволяет описать, как нужно обработать данные, кратко и емко.

Что такое Java Stream API

Это новый инструмент языка Java, который позволяет использовать функциональный стиль при работе с разными структурами данных.

Для начала стриму нужен источник, из которого он будет получать объекты. Чаще всего это коллекции, но не всегда. Например, можно взять в качестве источника генератор, у которого заданы правила создания объектов.

Данные в стриме обрабатываются на промежуточных операциях. Например: мы можем отфильтровать данные, пропустить несколько элементов, ограничить выборку, выполнить сортировку. Затем выполняется терминальная операция. Она поглощает данные и выдает результат.

Stream на примере простой задачи

Для наглядности посмотрим на примере использование стримов в сравнении со старым решением аналогичной задачи.

Задача — найти сумму нечетных чисел в коллекции.

Решение с методами стрима:

Здесь мы видим функциональный стиль. Без стримов эту же задачу приходится решать через использование цикла:

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

Преимущества Stream

Благодаря стримам больше не нужно писать стереотипный код каждый раз, когда приходится что-то делать с данными: сортировать, фильтровать, преобразовывать. Разработчики меньше думают о стандартной реализации и больше времени уделяют более сложным вещам.

Еще несколько преимуществ стримов:

  • Поддержка слабой связанности. Чем меньше классы знают друг про друга, тем лучше.
  • Распараллеливать проведений операций с коллекциями стало проще. Там, где раньше пришлось бы проходить циклом, стримы значительно сокращают количество кода.
  • Методы Stream API не изменяют исходные коллекции, уменьшая количество побочных эффектов.

Даже сложные операции по обработке данных благодаря Stream API выглядят лаконично и понятно. В общем, писать становится удобнее, а читать — проще.

Как создавать стримы

В таблице ниже — основные способы создания стримов.

Источник Способ Пример
Коллекция collection.stream() Collection collection = Arrays.asList(«f5», «b6», «z7»);

Stream collectionS = collection.stream();

Значения Stream.of(v1,… vN) Stream valuesS = Stream.of(«f5», «b6», «z7»);
Примитивы IntStream.of(1, … N) IntStream intS = IntStream.of(9, 8, 7);
DoubleStream.of(1.1, … N) DoubleStream doubleS = DoubleStream.of(2.4, 8.9);
Массив Arrays.stream(arr) String[] arr = <"f5","b6","z7">;

Stream arrS = Arrays.stream(arr);

Файл — каждая новая строка становится элементом Files.lines(file_path) Stream fromFileS = Files.lines(Paths.get(«doc.txt»))
Stream.builder Stream.builder().add(. ). build() Stream.builder().add(«f5»).add(«b6»).build()

Есть и другие способы. Например, с перечисляемыми типами можно создавать стримы не только из существующих последовательностей, но еще и задавать range , причем сразу двух типов:

Стримы можно создавать не только из файлов, но и из списка объектов какой-либо директории или файлов, находящихся в какой-либо части дерева файловой системы.

Если требуется параллельный стрим, то просто напишите collection.parallelStream() .

Почти все перечисленные способы создания потоков не выглядят необычно для тех, кто привык постоянно работать с коллекциями. Но есть еще два интересных варианта: Stream.iterate и Stream.generate . Их предназначение — бесконечные стримы.

В Stream.iterate мы задаем начальное значение, а также указываем, как будем получать следующее, используя предыдущий результат:

Stream iterStream = Stream.iterate(1, m -> m + 1)

Stream.generate позволяет бесконечно генерировать постоянные и случайные значения, которые соответствуют указанному выражению.

Stream generateStream = Stream.generate(() -> «f5»)

Если хотите узнать больше об этих и других способах, читайте документацию Stream.

Методы стримов

В Java 8 Stream API доступны методы двух видов — конвейерные и терминальные. Кроме них можно выделить ряд спецметодов для работы с числовыми стримами и несколько методов для проверки параллельности/последовательности. Но это формальное разделение.

Конвейерных методов в стриме может быть много. Терминальный метод — только один. После его выполнения стрим завершается.

Пока вы не вызвали терминальный метод, ничего не происходит. Все потому, что конвейерные методы ленятся. Это значит, что они обрабатывают данные и ждут команды, чтобы передать их терминальному методу.

Конвейерные

Метод Что сделает Использование
filter отработает как фильтр, вернет значения, которые подходят под заданное условие collection.stream().filter(«e22»::equals).count()
sorted отсортирует элементы в естественном порядке; можно использовать Comparator collection.stream().sorted().collect(Collectors.toList())
limit лимитирует вывод по тому, количеству, которое вы укажете collection.stream().limit(10).collect(Collectors.toList())
skip пропустит указанное вами количество элементов collection.stream().skip(3).findFirst().orElse(«4»)
distinct найдет и уберет элементы, которые повторяются; вернет элементы без повторов collection.stream().distinct().collect(Collectors.toList())
peek выполнить действие над каждым элементом элементов, вернет стрим с исходными элементами collection.stream().map(String::toLowerCase).peek((e) -> System.out.print(«,» + e)). collect(Collectors.toList())
map выполнит действия над каждым элементом; вернет элементы с результатами функций Stream.of(«3», «4», «5») .map(Integer::parseInt) .map(x -> x + 10) .forEach(System.out::println);
mapToInt , mapToDouble ,

mapToLong Сработает как map , только вернет числовой stream collection.stream().mapToInt((s) -> Integer.parseInt(s)).toArray() flatMap , flatMapToInt , flatMapToDouble , flatMapToLong сработает как map , но преобразует один элемент в ноль, один или множество других collection.stream().flatMap((p) -> Arrays.asList(p.split(«,»)).stream()).toArray(String[]::

Терминальные

Метод Что сделает Использование
findFirst вернет элемент, соответствующий условию, который стоит первым collection.stream().findFirst().orElse(«10»)
findAny вернет любой элемент, соответствующий условию collection.stream().findAny().orElse(«10»)
collect соберет результаты обработки в коллекции и не только collection.stream().filter((s) -> s.contains(«10»)).collect(Collectors.toList())
count посчитает и выведет, сколько элементов, соответствующих условию collection.stream().filter(«f5»::equals).count()
anyMatch True , когда хоть один элемент соответствует условиям collection.stream().anyMatch(«f5»::equals)
noneMatch True , когда ни один элемент не соответствует условиям collection.stream().noneMatch(«b6»::equals)
allMatch True , когда все элементы соответствуют условиям collection.stream().allMatch((s) -> s.contains(«8»))
min найдет самый маленький элемент, используя переданный сравнитель collection.stream().min(String::compareTo).get()
max найдет самый большой элемент, используя переданный сравнитель collection.stream().max(String::compareTo).get()
forEach применит функцию ко всем элементам, но порядок выполнения гарантировать не может set.stream().forEach((p) -> p.append(«_2»));
forEachOrdered применит функцию ко всем элементам по очереди, порядок выполнения гарантировать может list.stream().forEachOrdered((p) -> p.append(«_nv»));
toArray приведет значения стрима к массиву collection.stream().map(String::toLowerCase).toArray(String[]::new);
reduce преобразует все элементы в один объект collection.stream().reduce((c1, c2) -> c1 + c2).orElse(0)

Совет: подробнее изучите метод collect . Он позволяет гибко управлять преобразованием значений в разные типы: коллекции, массивы, map . Делается это благодаря статистическим методам Collectors .

Вот несколько интересных примеров:

  • toList — стрим приводится к списку;
  • toCollection — получаем коллекцию;
  • toSet — получаем множество;
  • toConcurrentMap , toMap — если нужен map ;
  • summingInt , summingDouble , summingLong — если требуется получить сумму чисел;
  • averagingInt , averagingDouble , averagingLong — если хотите вернуть среднее значение;
  • groupingBy — если необходимо разбить коллекцию на части.

Это не все статистические методы Collectors . Другие возможности с подробным описанием смотрите в документации. Помимо тех Collectors , которые определены в документации, можно использовать собственноручно созданные, кастомные варианты.

Методы числовых стримов

Это специальные методы, которые работают только со стримами с числовыми примитивами.

Метод Что сделает Использование
sum вернет сумму чисел, представленных в коллекции collection.stream().mapToInt((s) -> Integer.parseInt(s)).sum()
average вернет среднее арифметическое collection.stream().mapToInt((s) -> Integer.parseInt(s)).average()
mapToObj преобразует числовой стрим в объектный intStream.mapToObj((id) -> new Key(id)).toArray()

Еще несколько методов

Напоследок посмотрим еще несколько полезных методов, которые помогают управлять последовательными и параллельными стримами — как минимум быстро их определять.

Метод Что сделает Использование
isParallel скажет, параллельный стрим или нет someStream.isParallel()
parallel сделает стрим параллельным или вернет сам себя someStream = stream.parallel()
sequential сделает стрим последовательным или вернет сам себя someStream = stream.sequential()

Стримы могут быть последовательными и параллельными. Первые выполняются в текущем потоке, вторые используют общий пул ForkJoinPool.commonPool() . В параллельном стриме элементы разделяются на группы. Их обработка проходит в каждом потоке по отдельности. Затем они снова объединяются, чтобы вывести результат. С помощью методов parallel и sequential можно явно указать, что нужно сделать параллельным, а что — последовательным.

Не рекомендуется применять параллельность для выполнения долгих операций (например, извлечения данных из базы), потому что все стримы работают с общим пулом. Долгие операции могут остановить работу всех параллельных стримов в Java Virtual Machine из-за того, что в пуле не останется доступных потоков.

Чтобы избежать такой проблемы, используйте параллельные стримы только для коротких операций, выполнение которых занимает миллисекунды, а не секунды и тем более минуты.

В Stream API по умолчанию скрыта работа с потоконебезопасными коллекциями, разделение на части и объединение элементов. Это отличное решение. Разработчику остается только выбирать нужные методы и следить за тем, чтобы не было зависимостей от внешних факторов.

Решение задач с помощью Stream API

Давайте изучим на практике, как работать с разными методами Stream API , на примере несложных задач.

Допустим, у нас есть коллекция состоящая из строк. Arrays.asList(«Highload», «High», «Load», «Highload») . Применим к ней разные методы.

Посчитаем, сколько раз объект « High » встречается в коллекции:

А теперь посмотрим, какой элемент в коллекции находится на первом месте. Если мы получили пустую коллекцию, то пусть возвращается 0 :

Благодаря методам filter и findFirst можно находить элементы, равные заданным в условии:

Допустим, нам нужно вернуть последний элемент. Получили пустую коллекцию — пусть возвращается 0 . Используем метод skip , чтобы пропустить заданное количество элементов. А findFirst , чтобы вывести первое встреченное совпадение:

collection.stream().skip(collection.size() — 1).findFirst().orElse(«0») // Highload

С помощью метода skip можно искать элементы по порядку. Например, пропустить первый и вывести второй:

Можно также использовать методы skip и limit , чтобы явно задавать, сколько элементов нужно пропустить, а сколько — вернуть. Полученные значения соберем в массив:

collection.stream().skip(1).limit(2).toArray()// [High, Load]

Аналогичным образом можно поиграться с методами min и max . Пусть у нас будет коллекция строк вида Arrays.asList(«f10», «f15», «f2», «f4») . Найти самый маленький элемент не составит труда:

С максимальным значением тоже все очень просто:

Посмотрим несколько примеров работы сортирующих методов. Используем ту же коллекцию строк, что и выше — Arrays.asList(«f10», «f15», «f2», «f4», «f4») . Единственное отличие — теперь в нем появился дубликат.

Первая задача — отсортировать строки в алфавитном порядке и добавить их в массив:

collection.stream().sorted().collect(Collectors.toList()) // [f2, f4, f4, f10, f15]

А вот чуть более интересное задание — нужно выполнить сортировку в обратном алфавитному порядке и удалить дубликаты. В массиве должны оказаться только уникальные значения:

Здесь мы используем не только sorted для сортировки, но и метод distinct для удаления неуникальных значений при обработке коллекции.

Задачи про группу студентов

Теперь давайте посмотрим чуть более комплексные, взрослые задачи. Например, у нас есть коллекция, которая имеет следующий вид:

Мы можем обрабатывать эти данные используя методы Stream API . Например, давайте найдем средний возраст студентов мужского пола. Естественно, это может быть не целочисленное значение.

Сначала создадим коллекцию студентов и опишем их:

Теперь мы можем использовать методы стримов для обработки этой коллекции. Посчитаем средний возраст, используя метод average :

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

  1. Отфильтровали студентов по половому признаку.
  2. Выбрали для обработки их возраст.
  3. Вернули среднее арифметическое с помощью метода average.

Теперь давайте посмотрим, кому из наших студентов грозит получение повестки в этом году при условии, что призывной возраст установлен в диапазоне от 18 до 27 лет.

Не повезло Максиму. Он мужского пола, ему 20 лет. Другие студенты мужского пола не подходят под условие s.getAge() >= 18 && s.getAge() . Один младше 18 лет, другой — старше 27 лет. Единственная студентка в нашей выборке не подходит под условие s.getGender() == Gender.MAN . При этом по возрасту она вполне проходит по первым двум условиям. Но так как используется оператор && , в итоге мы получаем False .

Задачи на поиск в строке

Представим, что у нас есть большое количество логинов сотрудников. Нам нужно создать программу, которая будет выводить логины, начинающиеся на определенную букву. И использовать для решения этой задачи Stream API .

Вот как будет выглядеть код этой программы:

Программа предлагает ввести имена сотрудников. Все они сохраняются в массив ALL без предварительной обработки. Чтобы остановить ввод имен, нужно ввести пустую строку.

Сначала на экране выведется массив со всеми введенными именами. Чтобы отфильтровать их, нужно добавить условие. В нашем случае это будет первая буква — например, ‘ a ‘.

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

Заключение

Stream в Java дает разработчикам удобные инструменты для обработки данных в коллекциях. Методы позволяют проще обрабатывать объекты и писать меньше кода.

Но стрим — не серебряная пуля. Опытные разработчики собрали несколько советов по их использованию:

  1. Стримы можно не использовать, если задача решается красиво и эффективно без них.
  2. Не обязательно сохранять stream в переменную. Достаточно использовать цепочку вызовов методов.
  3. Старайтесь ограничить или почистить стрим от лишних элементов, прежде чем выполнять преобразования.
  4. Используйте параллельные потоки разумно. В отдельных случаях разбиение, обработка в разных потоках и последующее объединение данных могут быть дороже, чем работа в одном потоке.

Чтобы закрепить свои знания, посмотрите это наглядное пособие по Java Stream API . В нем рассматривается функциональный подход к работе с коллекциями. В видео есть примеры создания стримов из объектов файловой системы, примитивов, объектов, а также примеры использования конвейерных и терминальных методов:

Highload нужны авторы технических текстов. Вы наш человек, если разбираетесь в разработке, знаете языки программирования и умеете просто писать о сложном!
Откликнуться на вакансию можно здесь .

Источник

Читайте также:  Способы развития креативного мышления младших школьников
Оцените статью
Разные способы