Java 22: изменения в Stream API (с плохим API)



19 марта выйдет Java 22. Там появится интересная превью фича Stream Gatherers. Коротко расскажу, что это, зачем нужно, и что с ней не так.



Начнём. Код со Stream API делится на 3 части:

▫️ источник данных

▫️ промежуточные операции: map, filter, flatMap

▫️ терминальная операция: collect, count, findAny



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



Иногда их недостаточно. Например, хочется сделать distinct по какому-то признаку и дальше работать с исходными объектами:

clients.stream()

.distinct(Client::getBonusType)

.map(Client::getId).forEach(…)




Но это невозможно, distinct работает только по equals. В итоге надо либо извращаться, либо переписывать на for.



В Java 22 в Stream API появится универсальный метод gather. Туда можно передать логику преобразований с любыми отношениями - 1:1, 1:N, N:1, N:N. Синтаксис сложный, но круто, что такая возможность появилась.



Второе нововведение. Для collect есть готовые статические методы в классе Collectors, а для gather появится Gatherers с новыми методами. Самое полезное — два метода по работе с окнами:



🪟 windowFixed — поделить стрим на подмножества заданного размера

[1,2,3,4,5,6] → windowFixed(3) → [1,2,3], [4,5,6]




🪟 windowSliding — подмножества с пересечением одного элемента

[1,2,3,4,5,6,7] → windowSliding(3) → [1,2,3], [3,4,5], [5,6,7]




Что не так c методом gather?



1️⃣ Многословность



Та же ситуация, что и с collect. Чтобы разбить список на окна, придётся написать

list.stream()

.gather(Gatherers.windowFixed(2))

.collect(Collectors.toList())




Даже со статическим импортом выглядит не очень. Хочется писать без лишних слов:

list.stream()

.windowFixed(2)

.toList()




Да, исходный код Stream API сложный, и его тяжело расширять. Да, через статические методы реализация получится проще. Но не вижу ничего невозможного, чтобы сделать новые методы лаконичными, без приставки "gather(Gatherers"



2️⃣ Странные новые методы



Начнём с оконных. windowSliding делает пересечение только по одному элементу. Зачем нужно это ограничение — непонятно. Так же непонятно, зачем делать два отдельных метода windowFixed и windowSliding.



За образец можно взять Kotlin:

list.windowed(5,2)




Первый параметр задаёт размер окна, второй — шаг, с которым идём по списку. Удобно и понятно.



Ещё в Gatherers появятся три странных метода: fold, mapConcurrent и scan. С первого взгляда непонятно, зачем они нужны, очень уж специфичны.



В целом криминала в gather/Gatherers нет, жить можно. Но важный навык разработчика — замечать слабые места в своих и чужих решениях. Этот навык нужно развивать, для этого и нужен этот пост:)



Что ещё почитать:

🔥 Серия постов про коллекторы. Там же я рассказала, почему в стримах используется отдельный метод collect, а не просто toList()

🔥 Новые методы Stream API в Java 16

🔥 Критикую метод HashMap в Java 20. Хотя сам метод маленький, он показывает серьёзную ошибку проектирования API