Что такое коллекторы java
Применение интерфейса Collector для работы с потоками в реальных Java-проектах
Автор статьи, перевод которой мы публикуем сегодня, хочет рассказать о том, как интерфейс Collector и сопутствующие механизмы используются в реальных проектах.
Представим себе интернет-магазин, в котором есть корзина. Модель корзины выглядит так, как показано ниже.
Здесь и далее в коде будут использоваться комментарии в виде чисел, подробности о которых будут приводиться после блока кода.
В результате — для того чтобы узнать и цену товаров в отдельных строках, и общую стоимость заказа, нам нужно создавать на основе объекта корзины два потока. Из одного мы получаем сведения о строках, из второго — сведения о ценах.
Это — не особенно удачный способ решения нашей задачи.
Интерфейс | Описание |
Предоставляет базовый объект, контейнер, используемый для работы с потоком значений. | |
Описывает порядок накопления в контейнере значений, поступающих из потока. | |
Если работа ведётся с двумя потоками — описывает порядок их объединения. | |
Если тип мутабельного контейнера не является возвращаемым типом — описывает порядок преобразования контейнера в возвращаемый тип. | |
Предоставляет метаданные для оптимизации работы с потоками. |
Теперь мы можем реализовать интерфейс Collector :
Итоги
Код, проекта, который мы рассматривали, в формате Maven, можно найти в этом репозитории.
Пользуетесь ли вы классом Collectors и интерфейсом Collector в своих проектах?
Руководство по сборщикам Java 8
В статье рассматриваются коллекторы Java 8, показаны примеры встроенных коллекторов, а также показано, как создать пользовательский коллектор.
1. Обзор
Если вы хотите узнать, как использовать возможности коллекторов для параллельной обработки, проверьте этот проект.
Дальнейшее чтение:
Учебник по потоковому API Java 8
Руководство по Java 8 groupingBy Collector
Новые сборщики потоков в Java 9
2. Метод Stream.collect()
3. Коллекционеры
или просто отдельные коллекторы импорта по вашему выбору:
В следующих примерах мы будем повторно использовать следующий список:
3.1. Коллекторы.тоЛист()
3.1.1. Collectors.to НемодифицируЕмый список()
Java 10 представила удобный способ накопления элементов Stream в неизменяемый список :
3.2. Коллекторы.()
Set не содержит повторяющихся элементов. Если наша коллекция содержит элементы, равные друг другу, они появляются в результирующем Наборе только один раз:
3.2.1. Коллекторы.toUnmodifiableSet()
Начиная с Java 10, мы можем легко создать неизменяемый набор с помощью toUnmodifiableSet() collector:
Любая попытка изменить результирующий набор приведет к исключению UnsupportedOperationException :
3.3. Коллекционеры.()
Как вы, вероятно, уже заметили, при использовании для набора и перечисления коллекторов вы не можете делать никаких предположений об их реализации. Если вы хотите использовать пользовательскую реализацию, вам нужно будет использовать to Collection collector с предоставленной коллекцией по вашему выбору.
3.4. Коллекционеры.toMap()
Function.identity () – это просто ярлык для определения функции, которая принимает и возвращает одно и то же значение.
В таких случаях при столкновении ключей мы должны использовать для сопоставления с другой сигнатурой:
Третьим аргументом здесь является двоичный оператор |, в котором мы можем указать, как мы хотим обрабатывать коллизии. В этом случае мы просто выберем любое из этих двух сталкивающихся значений, потому что мы знаем, что одни и те же строки всегда будут иметь одинаковую длину.
3.4.1. Collectors.to Немодифицируемая карта()
Аналогично тому, как для List s и Set s, Java 10 представила простой способ сбора потока элементов в неизменяемую карту :
3.5. Коллекционеры.Коллекционирование и затем()
Сбор и затем – это специальный сборщик, который позволяет выполнять другое действие над результатом сразу после окончания сбора.
3.6. Коллекторы.присоединение()
Соединение collector может использоваться для соединения Stream элементов.
Мы можем объединить их, сделав:
Вы также можете указать пользовательские разделители, префиксы, исправления:
или вы можете написать:
3.7. Коллекторы.подсчет()
Теперь мы можем писать:
3.8. Коллекторы.суммирование Double/Long/Int()
Summarizing Double/Long/Int – это коллектор, который возвращает специальный класс, содержащий статистическую информацию о числовых данных в потоке извлеченных элементов.
Мы можем получить информацию о длине строк, выполнив:
В этом случае будет верно следующее:
3.9. Коллекторы.Усредненные/Длинные/Int()
Усреднение Double/Long/Int – это коллектор, который просто возвращает среднее значение извлеченных элементов.
Мы можем получить среднюю длину строки, выполнив:
3.10. Коллекторы.суммирование Double/Long/Int()
Суммирование Double/Long/Int – это коллектор, который просто возвращает сумму извлеченных элементов.
Мы можем получить сумму всех длин строк, выполнив:
3.11. Коллекционеры.maxBy()/minBy()
maxBy /|/minBy коллекторы возвращают самый большой/самый маленький элемент потока в соответствии с предоставленным экземпляром Компаратора .
Мы можем выбрать самый большой элемент, выполнив:
Обратите внимание, что возвращаемое значение завернуто в Необязательный экземпляр. Это заставляет пользователей переосмыслить угловой случай пустой коллекции.
3.12. Коллекционеры.()
Мы можем сгруппировать их по длине строки и сохранить результаты группировки в экземплярах Set :
Это приведет к тому, что следующее будет истинным:
3.13. Коллекторы.()
Вы можете написать:
В результате получается карта, содержащая:
3.14. Коллекционеры.()
К счастью, Java 12 предлагает встроенный сборщик, который заботится об этих шагах от нашего имени: все, что нам нужно сделать, это предоставить два сборщика и функцию объединения.
С этого нового коллекционера тройники данный поток в двух разных направлениях называется тизинг:
4. Пользовательские коллекторы
Если вы хотите написать свою реализацию коллектора, вам необходимо реализовать интерфейс коллектора и указать его три общих параметра:
Поскольку нам нужна изменяемая коллекция для обработки внутренних операций сбора, мы не можем использовать для этого ImmutableSet ; нам нужно использовать какую-либо другую изменяемую коллекцию или любой другой класс, который может временно накапливать объекты для нас.
В этом случае мы продолжим с ImmutableSet.Builder
и теперь нам нужно реализовать 5 методов:
Метод combined() возвращает функцию, которая используется для объединения двух аккумуляторов вместе:
Метод finisher() возвращает функцию, которая используется для преобразования накопителя в тип конечного результата, поэтому в этом случае мы просто будем использовать метод Builder ‘s build :
Вот полная реализация вместе с использованием:
5. Заключение
В этой статье мы подробно изучили Java 8 Collectors и показали, как его реализовать. Обязательно проверьте один из моих проектов, который расширяет возможности параллельной обработки в Java.
Класс Коллекционеров Java – 18 Примеров
Java Collectors-это служебный класс, который предоставляет множество полезных реализаций интерфейса коллектора. Реализация сборщика используется с
Java Collectors-это служебный класс, который предоставляет множество полезных реализаций интерфейса коллектора. Реализация сборщика используется с методом Stream collect (). Этот класс был представлен в Java 8 вместе с потоковым API. Коллекторы-это конечный класс, и все методы являются статическими, которые возвращают экземпляр Коллектора.
Методы коллекторов Java
Некоторые из популярных методов сбора Java являются:
Примеры коллекторов Java
Давайте рассмотрим примеры функций коллекторов на простых примерах.
1. Сбор средств(Поставщик)
Эта функция возвращает коллектор, который собирает входные элементы в коллекцию.
2. Java – коллекторы ToList()
Он возвращает сборщик, который собирает входные элементы в новый список.
Коллекционеры Java должны перечислять
3. Набор коллекторов Java()
Этот метод возвращает сборщик, который собирает входные элементы в новый набор.
4. Java – коллекторы toMap(Функция, Функция)
Этот статический метод возвращает коллектор для накопления входных элементов в новую карту. Мы должны указать функции отображения для создания ключей и значений карты.
5. Присоединение к коллекторам Java()
Он возвращает коллектор, который объединяет входные элементы последовательности символов в новую строку. Существует несколько перегруженных методов для указания разделителя и строк суффикса/префикса.
6. Отображение коллекторов Java(Функция, Коллектор)
Этот метод возвращает коллектор, который применяет функцию к входным элементам, а затем накапливает их в данный коллектор.
7. фильтрация(Предикат, Коллектор)
Он возвращает сборщик, который применяет Предикат к входным элементам и накапливает их в данный сборщик, если предикат возвращает значение true.
8. Сбор и затем(Сборщик, Функция)
Этот статический метод возвращает коллектор, который накапливает входные элементы в данный коллектор, а затем выполняет дополнительную функцию завершения.
9. подсчет()
Эта функция возвращает коллектор, который подсчитывает количество входных элементов.
10. минБи(компаратор)
Он возвращает коллектор, который возвращает минимальный элемент на основе данного компаратора.
11. Максби(компаратор)
Этот метод возвращает коллектор, который возвращает максимальный элемент на основе данного компаратора.
12. Функция суммирования(ToIntFunction)
Этот статический метод возвращает коллектор, который создает сумму целочисленной функции, примененной к входным элементам. Существуют аналогичные функции для long и double – summingLong(функция toLong) и summingDouble(функция ToDoubleFunction).
13. Усреднение(функция toInt)
Он возвращает коллектор, который выдает среднее арифметическое целочисленной функции, примененной к входным элементам. Существуют аналогичные функции для long и double – averagingLong(функция toLong) и averagingDouble(функция ToDoubleFunction).
14. Группировка коллекторов Java по(Функции)
Этот метод возвращает сборщик, реализующий операцию “группировка по” для входных элементов. Конечным результатом является хэш-карта. Существует несколько перегруженных методов для указания Поставщика (окончательный тип карты, по умолчанию-HashMap) и Сборщика (тип списка значений, по умолчанию-ArrayList).
15. Группирование по току(Функция)
Он работает так же, как и коллектор groupingBy (). Единственное отличие состоит в том, что сборщик является параллельным и неупорядоченным. Он будет иметь лучшую производительность, чем сборщик groupingBy (), но порядок не будет сохранен.
16. Разделение по(предикату)
17. Сокращение коллекторов Java(BinaryОператор)
Он возвращает коллектор, который выполняет сокращение своих входных элементов в соответствии с указанным двоичным оператором. Это в основном используется при многоуровневом сокращении, например, при указании нижестоящего коллектора с помощью методов группировки по() и разделения по ().
18. подведение итогов Int(функция toInt)
Он возвращает сборщик, который применяет функцию сопоставления, создающую int, к каждому входному элементу, и возвращает сводную статистику для результирующих значений, таких как min, max, среднее значение, количество и количество.
Вывод
Статические методы класса Java Collectors очень полезны при создании экземпляра коллектора для использования с методом Stream collect (). Он охватывает почти все самые популярные сценарии.
Новое в Java 12: The Teeing Collector
В этой статье мы рассмотрим новый коллектор, представленный в Java 12. Эта новая функция не была анонсирована в официальном JEP, поскольку это был минорный change request с заголовком «Create Collector, which merges the results of two other collectors». Она предназначена для объединения результатов с двух коллекторов.
Все интересное — под катом
Документация
«… возвращает коллектор, составленный из двух нижестоящих коллекторов. Каждый элемент переданный в результирующий коллектор, обрабатывается обоими нижестоящими коллекторами, а затем их результаты объединяются с помощью специальной функции, которая соединяет их в конечный результат.»
Интересный факт
Это тройник(teeing с англ.):
Teeing произошел от тройника. Согласно Википедии, «тройник — самый распространенный фитинг(соединительная часть трубопровода, прим. переводчика) используемый для объединения[или разделения] потока жидкостей(в данном случае имеются ввиду стримы, stream — ручей/поток, прим. переводчика)».
Предлагались и другие имена: bisecting(разделение_на_2_части), duplexing, bifurcate(раздвоение), replicator, fanout(разветвление), tapping, unzipping, collectionToBothAndThen, biCollecting, expanding(расширение), forking, и т.д.
Все альтернативы, оцененные разработчиками Core, можно посмотреть здесь.
Примеры использования
Я собрал три примера использования кода с разными уровнями сложности.
Список гостей
Мы извлекаем два разных типа информации из списка объектов в потоке. Каждый гость должен принять приглашение и может привести семью. Мы хотим знать, кто подтвердил бронирование и общее количество участников(включая гостей и членов семьи).
Отфильтровать имена в двух разных списках
В этом примере мы разделяем поток имен на два списка в соответствии с фильтром.
Посчитайте и сложите стрим из чисел
В следующем примере используются функции из Teeing для возврата двух значений:
Возможная ловушка
Map.Entry
Все о новых фичах Java 12
Вы можете узнать больше информации и интересных фактов о Java 12 в этой презентации.
Что нужно знать о Java Stream API
Авторизуйтесь
Что нужно знать о Java Stream API
Java-разработчик
Всем привет! В этой статье я хочу познакомить вас, на мой взгляд, с одним из самых значительных нововведений в Java со времен ее появления — это Java Stream API.
Что такое Java Stream API? Зачем? И какие дает преимущества?
Очень часто, когда мы пишем программу, нам нужно обрабатывать наши данные. Для обработки данных мы используем циклы либо рекурсивные функции для обхода наших данных.
Java Stream API был создан для того, чтобы помочь пользователям ускорить и упростить обработку данных. Сам по себе API предоставляет инструмент, который позволяет нам дать рецепт того как обрабатывать объекты.
Если проводить параллели с реальным миром, то давайте представим, что у нас есть некий завод по производству мебели под заказ.
18 декабря, Онлайн, Беcплатно
Грузовые автомобили привозят бревна на завод. На данном заводе у нас есть люди которых мы обучили что-то делать с древесиной, чтобы из нее получилась мебель: они просматривают каждое бревно на предмет дефектов и отфильтровывают брак, распиливают бревна, обрабатывают доски, собирают при помощи гвоздей и клея и защищают готовую продукцию при помощи лака.
Последний элемент в этой цепи — покупатель, который приходит на завод и делает заказ.
Без покупателя нет смысла запускать все производство, поэтому весь процесс стартует во время запуска производства.
В мире Java такой завод называется Stream API. Этот API представляет собой библиотеку, которая помогает в функциональном стиле кратко, но емко описывать, как обработать данные.
Как и в примере про завод, у каждого стрима должен быть источник объектов. Этим источником информации чаще всего бывает коллекция, так как именно в них мы и храним наши данные, но это не обязательно — может быть и какой-то генератор, который генерирует объекты по заданному правилу, примеры мы рассмотрим позже.
В Java Stream API также предусмотрены промежуточные операции. Они выполняют роль рабочих. Операции описывают процесс обработки объектов.
В конце каждого стрима должна быть терминальная операция, которая должна поглотить все обработанные данные.
В примере про завод мы видели, что заказчик становится триггером начала производства и является последним звеном в работе завода — он забирает всю продукцию.
Рассмотрим простейший стрим. Создадим класс бревно и поместим несколько бревен в коллекцию:
Получив ссылку на стрим, мы можем начать обрабатывать поток наших данных.
Отфильтруем бревна, количество которых меньше 7 и оставим только те, которые не являются дубом. Выглядеть это будет так:
Мы добавили фильтры и получили стрим, в котором описан процесс обработки всех наших бревен. Теперь мы должны добавить к нему терминальную операцию, чтобы запустить поток данных из коллекции:
В этом примере конечная операция принимает оставшиеся элементы после фильтрации и распечатывает их. Стоит особо упомянуть, что второй раз вызвать терминальную операцию не получится — стрим является «одноразовым» объектом. Это сделано авторами библиотеки для того, чтобы можно было корректно обрабатывать данные, которые имеют ограниченное время жизни. Например, если обрабатывать пакеты из интернета, то данные в стрим могут попасть только один раз, поэтому повторный вызов теряет всякий смысл.
Как упоминалось ранее, создать источник данных можно разными способами. Рассмотрим самые популярные.
Способы создания источника данных
В начале пройдемся по методам объявленным в интерфейсе Stream.
Stream.of(). Метод принимает массив объектов и создает на их основе стрим.
Для создания пустого стрима существует метод:
Патерн строитель поддерживается библиотекой, потому получив объект строителя Stream.builder() мы можем сконструировать с помощью него новый стрим.
Если у нас есть два стрима, мы можем объеденить их в один вызвав метод:
В итоге мы получим стрим, в котором будет находится шесть элементов.
Стрим не обязательно должен поглощать какие-то данные, можно создать генератор, который будет поставлять в наш стрим с помощью метода generate()
Так как генератор может бесконечно генерировать стрим и в примере выше мы получим бесконечный вывод на экран случайных значений, необходимо добавить промежуточную операцию limit(100) — она позволит ограничить стрим. С этими операциями мы познакомимся позже.
Аналогичную функциональность предоставляет класс Random. В нем уже есть методы которые создают стримы из случайных чисел.
Тут стоить отметить, что порой, когда стрим состоит из одних чисел, использование оберток над примитивными типами будет сильно влиять на производительность.
Поэтому создатели стримов добавили специальные типы стримов для примитивных типов:
Это такие же стримы, но как понятно из названия оперируют они только одним типом данных.
Теперь мы перейдем к самому интересному — в интерфейсе Collection добавлен дефолтный метод, который возвращает нам стрим. То есть любая коллекция дает нам возможность превратить ее в стрим:
Просто вызвав метод у коллекции мы получили стрим. Это самый частый способ получить стрим из набора данных.
Познакомившись с основными методами создания теперь мы можем перейти к промежуточным операциям. Именно они позволят нам обработать наш поток данных.
Промежуточные операции
Мы ранее уже знакомились с операцией фильтр, она позволяет нам написать выражение, которое будет проверятся для каждого элемента и если выражение истинно, то элемент может проходить дальше.
Но на нашем заводе мы делаем намного больше чем просто фильтруем бревна. Для того, чтобы дерево превратилось в мебель его нужно преобразовать.
Для этого пригодится самая популярная функция — map().
Возьмем наш пример выше и попробуем преобразовать
Промежуточные операции можно конкатенировать между собой, то есть мы можем добавить еще несколько преобразований:
Во втором преобразовании мы разбили каждую строку на массив строк. Но если мы запустим приложение, мы увидим, что на экран не вывелись строки, а вывелось toString() массивов. Нам хочется чтобы стрим был плоский — то есть только из объектов, а не из других стримов/массивов в которых есть объекты. Для этого авторы Java Stream API придумали еще одну промежуточную операцию — flatMap. Вот как она позволит изменить нам наш стрим (для более краткой записи я заменил прошлые операции на метод референс):
Но запустив пример выше мы получили стрим стримов — Stream
Но так с ним работать не удобно, а обычный flatMap не сработает, поэтому для примитивных стримов существуют специальные операции для их преобразований:
Для того чтобы отсортировать буквы воспользуемся операцией sorted() :
Стоит отметить, что операция sorted() таит в себе некоторые проблемы. Так для того чтобы отсортировать объекты, поступающие из стрима, она должна аккумулировать в себе все объекты, которые есть в стриме и только потом приступить к сортировке. Но что делать, если стрим бесконечный либо в стриме огромное количество элементов? Вызов такой операции приведет к OutOfMemoryException.
Простой пример приведен ниже:
Стрим будет генерировать новые значения, пока остаток от деления на 7 сгенерированного значения не будет равен 0.
Терминальные операции
После знакомства с основными промежуточными операциями мы плавно подошли к заключению. Осталось рассмотреть терминальные операции. Это операции, которые как бы «запускают» наш стрим. Мы можем создать стрим и добавить в него любое количество промежуточных операций, но они не будут выполнены пока не будут добавлена терминальная операция.
Кроме этого терминальная операция может и возвращать значение. Рассмотрим самые распространенные — findFirst(), findAny(), anyMatch(), allMatch(), noneMatch().
Теперь стоит перейти к более сложным функциям. Часто в качестве результата стрима мы хотим получить набор из новых объектов, которые были созданы в результате обработки. Для этого удобно поместить их в массив или коллекцию.
В Java Stream API было добавлено несколько методов, которые дают соответствующую функциональность.
Вызвав терминальную операцию Object[] toArray() мы получим ссылку на массив, в котором будет находится все объекты. Если нужно вернуть массив определенного типа, то в метод стоить передать IntFunction generator на вход функции поступит число элементов, а внутри нее мы должны создать нужный нам тип массива.
Следующая операция, которую стоить упомянуть T reduce(T identity, BinaryOperator accumulator) — в нее передается начальное значение и бинарная функция, которая задает алгоритм объединения двух объектов.
Для того, чтобы получить сумму первых 100 членов стрима из произвольных значений, запишем:
Мы передаем начальный элемент для сложения, в нашем случае он 0 и бинарную функцию, которая описывает как объединить два значения из стрима.
Если же мы хотим перенести этот набора чисел в коллекцию, то для этого нам надо будет указать как создать коллекцию и как в нее помещать элементы:
В функции reduce мы передали наш начальный аргумент — новую пустую коллекцию. Потом описали правило, по которому будем объединять коллекцию и элементы стрима.
И в конце описали как мы будем объединять две коллекции.
То есть вся логика комбинирования элементов хранится в структуре данных под названием коллектор.
Создатели Java Stream API добавили в библиотеку большое количество коллекторов, рассмотрим их.
Существует более общий метод Collectors.toCollection(). В качестве аргумента в нее можно передать коллекцию, в которую будут помещены элементы стрима.
Операция partitionBy() позволяет разделить стрим на два множества по определенному условию. Например мы хотим разделить наш буквенный стрим на две группы с большими буквами и прописными:
Коллекторы могут быть скомбинированы друг с другом, что дает большую гибкость.
В примере выше мы видим, что некоторые буквы повторяются, мы этого не хотим поэтому добавим еще один коллектор, который соберет все в Set:
Чтобы самостоятельно реализовать коллектор можно воспользоваться статическим методом:
В этой короткой статье мы познакомились с, на мой взгляд, самой крутой штукой в языке Java с момента ее создания. Стримы позволяют существенно упростить, а соответственно ускорить разработку кода. Возможность практически бесплатно сделать стрим параллельным, тем самым повысив производительность кода в разы, делает стримы инструментом номер одни в руках каждого разработчика.