Function identity java что это
Represents a function that accepts one argument and produces a result. This is a functional interface whose functional method is apply(Object) .
Method Summary
| Modifier and Type | Method and Description |
|---|---|
| default Function | andThen (Function after) |
Returns a composed function that first applies this function to its input, and then applies the after function to the result.
Applies this function to the given argument.
Returns a composed function that first applies the before function to its input, and then applies this function to the result.
Returns a function that always returns its input argument.
Method Detail
apply
R apply(T t)
Applies this function to the given argument.
compose
default FunctionR> compose(FunctionT> before)
Returns a composed function that first applies the before function to its input, and then applies this function to the result. If evaluation of either function throws an exception, it is relayed to the caller of the composed function.
andThen
default FunctionT,V> andThen(FunctionR,? extends V> after)
Returns a composed function that first applies this function to its input, and then applies the after function to the result. If evaluation of either function throws an exception, it is relayed to the caller of the composed function.
identity
static Function identity()
Практические примеры использования Stream API
Всем привет! Решил написать статью с практическими примерами использования Stream API. В данной статье не будет теории — только хардкор и практические примеры. Поехали!
Сразу хочу отметить: данная статья написана исключительно в целях демонстрации основ работы Stream API и структура проекта, используемая в ней для примера, не подходит для применения на реальных проектах 😉
В качестве «подопытного кролика» буду использовать проект, который можете найти здесь.
Для тестирования буду использовать Postman.
В проекте есть три сущности с которыми мы будем работать: Client, Product и Booking. Связи между ними можно посмотреть на следующей картинке.

В сущности Product есть enum Category. Центральная сущность — Booking, в которую входит Client, List, а также enum Status. То есть Booking — это заказ перечня продуктов, который делает клиент.
Я в проекте буду использовать базу данных H2. Для просмотра данных можно после запуска проекта пройти по ссылке http://localhost:8080/h2-console , ввести там username: user и password: password и вы увидите это:

Доступ к данной штуке происходит за счет spring.h2.console.enabled=true, которую я прописал в application.properties, здесь также можно поменять логин и пароль доступа к базе данных.
Генерацию таблиц сделает за нас Hibernate за счет spring.jpa.hibernate.ddl-auto=create, которая прописана в application.properties, так как прописано create — при каждом запуске проекта будут удалятся и создаваться заново таблицы в базе данных. Это можно увидеть в логах так как прописаны spring.jpa.show-sql=true и spring.jpa.properties.hibernate.format_sql=true.
Для генерации данных в таблицы базу я написал следующих класс
@Component public class DataLoader < private final long NUMBER_BOOKINGS = 30; @Bean public CommandLineRunner loadDataClient(ClientRepository clientRepository, ProductRepository productRepository, BookingRepository bookingRepository) < return (args) ->< Faker faker = new Faker(); long indexForProduct = 1; for (long i = 0; i products = new HashSet<>(); long indexToIncreaseProduct =0; for (long j = indexForProduct; j indexForProduct += indexToIncreaseProduct; Client client = clientRepository.save(new Client(i, faker.name().firstName() + " " + faker.name().lastName())); bookingRepository.save(new Booking(i, LocalDate.of(2022, Month.JANUARY, 1).plusDays(faker.number().numberBetween(1, 365)), Status.valueOf(Status.values()[faker.number().numberBetween(0, 3)].name()), client, products)); > >; > >
Данный код генерирует 30 заказов, клиентов и случайное количество продуктов в заказе и сохраняет все это в базу данных. Число заказов можете менять на свое усмотрение. Для генерации данных я использовал стороннюю библиотеку Faker, кому интересно можете про нее почитать тут https://www.baeldung.com/java-faker.
С вводной частью все можно приступать к Stream API.
Пример 1. Найти все продукты категории «Book», с ценой более 50
@Override public List findAllProductsBelongsCategoryBookWithPriceMore50() < return productRepository.findAll() .stream() .filter(product ->product.getCategory().equals(Category.BOOK)) .filter(product -> product.getPrice() > 50) .collect(Collectors.toList()); >
Находим все продукты в базе данных, потом применяем два фильтра, первый оставляет нам только продукты категории «Book», а второй сравнивает цену с условием и оставляет только продукты, где цена больше 50. В конце делаем терминальную операцию и собираем наш поток в список продуктов, но уже только с теми, которые прошли все фильтры.
Для тестирования данного кода можете в Postman отправить запрос http://localhost:8080/api/v1/product/findAllProductsBelongsCategoryBookWithPriceMore50

или что-то похожее, так как первоначальные данные будут у всех генерироваться разные. Также для тестирования всех примеров есть два контроллера (BookingController и ProductController) с написанными эндпоинтами, вызывая которые можно протестировать примеры.
Пример 2. Найти все продукты, заказанные за определенный, заданный период времени
@Override public List findAllProductOrderedBetweenDates(LocalDate start, LocalDate finish) < return bookingRepository.findAll() .stream() .filter(booking ->booking.getOrderDate().compareTo(start) >= 0) .filter(booking -> booking.getOrderDate().compareTo(finish) booking.getProducts().stream()) .collect(Collectors.toList()); >
Находим все заказы, после применяем два фильтра, которые фильтруют наши заказы, чтобы дальше прошли только заказанные в заданный период. После применяем flatMap(), которая делает один новый stream из всех продуктов в заказах. Последняя – это терминальная операция, которая объединяет все продукты в один список.
Для тестирования этого примера нам необходимо в Postman, также передать даты периода, за который мы хотим сделать выборку.

Пример 3. Найти самый дешевый продукт в категории «Medicine»
@Override public Optional findCheapestProductInCategoryMedicine() < return productRepository.findAll() .stream() .filter(product ->product.getCategory().equals(Category.MEDICINE)) .min(Comparator.comparing(Product::getPrice)); >
Получаем stream со всеми продуктами, потом применяем фильтр, который оставляет только продукты категории “Medicine” и потом с помощью операции min() находим самый дешевый продукт.
Пример 4. Найти все продукты, заказанные в определенный день
@Override public List findAllProductOrderedInDate(LocalDate date) < return bookingRepository.findAll() .stream() .filter(booking ->booking.getOrderDate().isEqual(date)) .flatMap(booking -> booking.getProducts().stream()) .collect(Collectors.toList()); >
Находим все заказы, после применяем фильтр, где мы сравниваем дату нашего заказа с заданной датой. После применяем flatMap(), которая делает один новый stream из всех продуктов в заказах. Последняя – это терминальная операция, которая объединяет все продукты в один список.
Пример 5. Получить статистические данные по продуктам категории “Food”
@Override public DoubleSummaryStatistics obtainCollectionOfStaticAllProductsCategoryFood() < return productRepository.findAll() .stream() .filter(product ->product.getCategory().equals(Category.FOOD)) .mapToDouble(Product::getPrice) .summaryStatistics(); >
Получаем stream со всеми продуктами, потом применяем фильтр, который оставляет только продукты категории “ Food ”, а затем с помощью операции mapToDouble() образуем один поток данных с ценами всех продуктов данной категории и после с помощью терминальной операции summaryStatistics() получаем статистические данные.
Отправив в Postman запрос http://localhost:8080/api/v1/product/obtainCollectionOfStaticAllProductsCategoryFood мы получим статистические данные:

у вас конкретные значения будут отличаться.
Пример 6. Сгруппировать продукты по категориям
@Override public Map> getMapWithListProductsNameByCategory() < return productRepository.findAll() .stream() .collect(Collectors.groupingBy( product ->product.getCategory().name(), Collectors.mapping(Product::getName, Collectors.toList()) )); >
Первоначально получаем список всех продуктов и делаем из него поток. После с помощью операции groupingBy() задаем по чем мы хотим группировать продукты (по имени категории) и с помощью операции mapping() указываем какие данные должны быть собраны в список и относиться к данной категории.
Пример 7. Получить самые дорогие продукты по категориям
@Override public Map> getMostExpensiveProductByCategory() < return productRepository.findAll() .stream() .collect( Collectors.groupingBy( product ->product.getCategory().name(), Collectors.maxBy(Comparator.comparing(Product::getPrice))) ); >
Первоначально получаем список всех продуктов и делаем из него поток. После с помощью операции groupingBy() задаем по чем мы хотим группировать продукты (по имени категории) и с помощью операции maxBy() указываем какой продукт (в нашем случае самый дорогой) должен относиться к данной категории.
Пример 8. Получить все заказы, которые принадлежат категории “Sport”
@Override public List findAllBookingWithProductsBelongCategorySport() < return bookingRepository.findAll() .stream() .filter(booking ->booking.getProducts().stream().anyMatch(product -> product.getCategory().equals(Category.SPORT))) .collect(Collectors.toList()); >
Находим все заказы и делаем из него поток. После применяем фильтр, в котором из каждого заказа достаем список продуктов и делаем из него поток и с помощью операции anyMatch() проверяем есть ли в данном списке хотя бы один продукт с категорией “Sport”. После все собираем в один список с помощью терминальной операции collect().
Пример 9. Получить три последних заказа
@Override public List findThreeMostRecentBooking()
Получаем список заказов и делаем из него поток, далее сортируем данный поток с помощью операции sorted(), где в качестве того по чем мы будем сортировать передаем дату заказа (Booking::getOrderDate) и так как список будет отсортирован от самого старого заказа до самого последнего, то необходимо применить операцию reserved(), чтобы список развернуть в обратном направлении – от самого нового заказа до самого старого. Операция limit() оставляет только нужное количество нам элементов и последняя терминальная операция собирает все в список.
Пример 10. Посчитать общую сумму всех заказов за определенный период
@Override public Double calculateTotalSumAllBookingsBetweenDates(LocalDate start, LocalDate finish) < return bookingRepository.findAll() .stream() .filter(booking ->booking.getOrderDate().compareTo(start) >= 0) .filter(booking -> booking.getOrderDate().compareTo(finish) < 0) .flatMap(booking ->booking.getProducts().stream()) .mapToDouble(Product::getPrice) .sum(); >
Получаем список заказов, после применяем два фильтра, с помощью которых оставляем только заказы с нужного нам диапазона времени. Затем с помощью операции flatMap() делаем общий поток всех продуктов и применяем операцию mapToDouble(), которая делает из общего потока продуктов поток цен. Завершающая терминальная операция sum() суммирует все числа.
Пример 11. Найти среднее значение стоимости всех заказов со статусом APPROVED за определенную дату
@Override public Double calculateAverageAllBookingsWithStatusApprovedOnDate(LocalDate start) < return bookingRepository.findAll() .stream() .filter(booking ->booking.getOrderDate().isEqual(start)) .filter(booking -> booking.getStatus().equals(Status.APPROVED)) .flatMap(booking -> booking.getProducts().stream()) .mapToDouble(Product::getPrice) .average() .orElse(0); >
Находим все заказы, после применяем два фильтра. Первый оставляет только заказы за определенную дату, а второй выбирает только заказы со статусом APPROVED. Далее с помощью операции flatMap() из всех заказов достаем списки продуктов и делаем из них общий поток и с помощью операции mapToDouble(), делам из общего потока продуктов поток их цен. Завершающая терминальная операция average() находит среднее значение данных цен.
Пример 12. Получить Map<>, где ключ – Id заказа, а значение — количество продуктов в данном заказе
@Override public Map getMapWithBookingIdAndCountProduct() < return bookingRepository.findAll() .stream() .collect(Collectors.toMap(Booking::getId, booking ->booking.getProducts().size())); >
Получаем список всех заказов и делаем из него поток. После применяем операцию collect(), где указываем, что хотим собрать все в Map<> и указываем, что ключом будет являться Id заказа, а значением — количество продуктов в данном заказе.
Пример 13. Получить Map<>, где ключ – клиент, а значение — список заказов, которые сделал данный клиент
@Override public Map> getMapWithClientAndListBookings()
Получаем список всех заказов и делаем из него поток. После применяем операцию collect(), где указываем, что хотим сгруппировать по клиентам и все. На сколько упрощается код при использовании stream API.
Пример 14. Получить Map<>, где ключ – заказ, а значение — общая сумма стоимости всех продуктов в нем
@Override public Map getMapWithBookingAndProductTotalSum() < return bookingRepository.findAll() .stream() .collect(Collectors.toMap( Function.identity(), booking ->booking.getProducts().stream() .mapToDouble(Product::getPrice).sum() )); >
Получаем список всех заказов и делаем из него поток. После применяем операцию collect(), где указываем, что хотим собрать все в Map<> и указываем, что ключом будет являться заказ (Function.identity()), а значением — мы из всех заказов достаем списки продуктов и делаем из них общий поток и с помощью операции mapToDouble(), делам из общего потока продуктов поток их цен. Завершающая терминальная операция sum() находит сумму данных цен.
Всем спасибо кто дочитал до конца.
Всем удачи в изучении stream API.
Лямбды в Java 8, что лучше использовать — Function.identity() или t->t?
Должен ли я использовать Function.identity() вместо str->str (или наоборот)? Я думаю, что второй вариант более читабелен и понимаем(допустим, если новый человек не знает что делает identity ). Но есть ли «реальная» причина, из-за которой следует отдать предпочтение одному из способов?
Отслеживать
13.7k 12 12 золотых знаков 43 43 серебряных знака 75 75 бронзовых знаков
задан 9 апр 2019 в 11:14
Anton Sorokin Anton Sorokin
6,998 6 6 золотых знаков 37 37 серебряных знаков 65 65 бронзовых знаков
ассоциация: stackoverflow.com/questions/28032827/…
9 апр 2019 в 11:14
И про collect кто-то может не знать, и про map , если этот кто-то знаком с функциональным программированием меньше недели. Если писать код, который смогут прочитать даже такие специалисты, увы, только они этот код и станут читать.
9 апр 2019 в 11:20
@extrn невозможно помнить все API и методы библиотеки(любой). identity() — не такой важный метод, чтобы всегда помнить что он делает.
9 апр 2019 в 11:30
Термин Identity function известен далеко за пределами java API , и реализации такой функции присутствуют во многих функциональных языках: Common Lisp, Haskell, F#, Scala и т.д.
9 апр 2019 в 11:50
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
Первоначально кажется, что у этих двух способов нет отличия, ведь реализация identity() такова:
static Function identity() < return t ->t; >
Однако Function.identity() всегда будет возвращать один и тот же экземпляр(обоснование этого решения), в то время как t -> t не только будет создавать новый экземпляр, но даже будет иметь отдельный класс реализации. Для более подробной информации смотрите здесь.
Причина в том, что компилятор генерирует синтетический метод, содержащий тело лямбда выражений(случае t -> t это будет return t; ) и указывает среде выполнения создать реализацию функционального интерфейса, вызывающего этот метод.
Поэтому использование Function.identity() вместо t -> t может сэкономить немного памяти, но совсем небольшое количество, поэтому это не должно влиять на ваше решение, если вы считаете, что t -> t более читабельно, чем Function.identity() .
Также в некоторых методах нельзя вызвать Function.identity() . Допустим, есть такой список:
List list . .
Код ниже отлично скомпилируется:
int[] arrayOK = list.stream().mapToInt(i -> i).toArray();
Но если попытаться скомпилировать следующий код, то будет ошибка компиляции:
int[] arrayProblem = list.stream().mapToInt(Function.identity()).toArray();
Она произойдет из-за того что mapToInt ожидает ToIntFunction , которая не связана с Function . Также у ToIntFunction нет метода identity() .
Разница между Function.identity() и t->t в Java 8
При работе с Java 8 и её функциональными возможностями столкнуться с выбором между использованием Function.identity() и лямбда-выражением t->t . Так, например, при преобразовании потока элементов в map, можно выбрать один из двух вариантов:
List<String> list = Arrays.asList("a", "b", "c"); Map<String, String> map1 = list.stream() .collect(Collectors.toMap(Function.identity(), Function.identity())); Map<String, String> map2 = list.stream() .collect(Collectors.toMap(t -> t, t -> t));
В обоих случаях результат будет одинаковым. Так, для чего же нужен Function.identity() , если его можно заменить на, казалось бы, более простое и понятное лямбда-выражение?
Во-первых, Function.identity() возвращает функцию, которая всегда возвращает свой входной аргумент. Это полезно в тех случаях, когда требуется явный функциональный идентификатор. Лямбда-выражение t -> t делает то же самое, но не обладает такой явной идентификацией.
Во-вторых, Function.identity() является предопределенной функцией и, следовательно, не требует дополнительной анонимной функции при каждом вызове, в отличие от лямбда-выражения t -> t . Это может быть полезно с точки зрения производительности, особенно при работе с большими объемами данных.
В-третьих, использование Function.identity() делает код более читаемым и понятным для других разработчиков, так как явно указывает на намерение возвращать тот же объект, что и принятый на вход.
Таким образом, выбор между Function.identity() и t -> t во многом зависит от конкретной ситуации и личных предпочтений разработчика. В целом, можно сказать, что Function.identity() является более предпочтительным вариантом, так как обладает рядом преимуществ, связанных с производительностью и читаемостью кода.