Репутація українського ІТ. Пройти опитування Асоціації IT Ukraine
×Закрыть

Три паззла о Java 8 и Streams API

Всем привет

В предверии тренинга по Java 8/9(dou.ua/calendar/14295) я разбирал различные паззлы, которые мне встречались во время изучения Java 8 и подготовки семинаров.
Понятно, что паззл редко встречается в рабочих проектов, но с другой стороны, чем быстрее и точнее можешь в нем разобраться, тем больше у тебя опыта и навыков в данной компетенции.

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

1)

IntStream stream = IntStream.range(0, 10);
		Stream stream2 = (Stream) stream;
		System.out.println(stream2.limit(5).count());

2)

		IntStream.iterate(0, i -> 1).distinct().limit(5).forEach(System.out::println);

3)

		List<String> list = new ArrayList<>();
		list.add("1");
		list.add("2");		
		list.stream().sorted().
			peek(list::remove).
			forEach(System.out::println);		
		System.out.println(list.size());
LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

1. по идее не закастится так просто, для примитивов кучу методов пришлось наплодить в тримах, особенно для мэппинга
2. limit не отдаст конец потока пока не наберет 5 элементов, а distinct не отдаст ему 5 элементов по понятным причинам
3. нарушение контракта, поведение неопределено

Спасибо. Сколько вы уже используете Java 8 на своих проектах?

Если речь о стримах, то почти год )

вообще-то в документации по лимиту указано «не более 5»

Верно, я неточно написал, т.к. имел ввиду условие по которому limit отдаст конец потока до того как он прервется извне.
PS в 2ом примере можно кстати еще злее написать: iterate(1, x -> x++)

Спасибо, это очень интересный пример

2) треба переписати як
IntStream.iterate(0, i -> 1).distinct().limit(5).forEach(System.out::println); System.out.println("I'm very smart");

System.out.println("0″);
System.out.println("1″);
System.out.println("1″);
System.out.println("1″);
System.out.println("1″);
System.out.println("Зато мой код понятный");
System.out.println("И даже школьник может его адаптивровать под свои нужды");

1. каст не сработает, потому что оба интстрим и стрим наследуют бейзстрим. Так себе паззл, надо лезть в доку и смотреть. Но подозрение упало сразу на каст.
2. вроде должно работать, но не работает. Наверное, из-за того что distinct сравнивает по Object.equal, а с примитивами ломается.
3. вот это не знал. то что так делать нельзя это 100% :)

э! э! пункт 2: просили ж «не запуская»

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

а если стрим конечный — он тоже будет ждать пять?

во-первых, в доке написано не «точно 5», а «до 5»
да это даже семантически ясно сразу

во-вторых, стримы ленивые — раз, нетерминальные операции порождают новый стрим — два

отсюда: если это нормальный jvm (а других вроде и нет), бесконечный стрим будет оптимизирован до 5 реальных итераций, после чего через дистинкт пролезет 0, 1, что вполне соответствует лимит(5), который породит такой же, но новый стрим 0,1, для которого реально и исполнится терминальный форич

могли, до речі, java архітектори і якийсь .timeout() для стрімів запилити, щоб відвалювалось при надто довгому очікуванні.

ви отеті свої імперативні звички вказувать умній функціональній машині як ій працювать — давайте кидайте!
вона луччє зна!

Ну в RxJava например есть тайм-ауты, может быть и в Java 10 появятся.

чем быстрее и точнее можешь в нем разобраться, тем больше у тебя опыта и навыков в данной компетенции.
«В данной компетенции» — это в каокой? Решение пазлеров?
Сейчас будет обидно:
Доклады/темы про пазлеры — это явный признак бездельника (мягко говоря) в наши дни.

такие темы довольно часто проскивают на больших конференциях в Украине)

вся суть подібних конференцій

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

Ну можно как угодно называть спикера, но только не бездельником.
1) Я и не говорил про __всех__ спикеров.
2) «Бездельник» тут больше в смысле «технического профессионализма»
3) Если бы я называл вещи своими именами, то админы ДОУ, попросту удалили бы коммент. :)
Ведь человек должен подготовить тему, найти паззлы,
Работы на 1-2 вечера. Я проверял :)
подготовиться так, чтобы слушателям было интересно и т.д.
А это уже другая история. Всю это туму с пазлерами «возродил», кажись, Барух, он таки шоумен, и суть пазлеров была как раз в шоу, а не в контенте. Формат довольно простой по сравнения с техническими докладами, поэтому куча «бездельников» (снова же не хочу чтобы коммент удалили) радостно заскочила на волну.

1) Скомпилится и будет работать как ни в чем не бывало. А чего б не, к своим-то деталям реализации класс имеет доступ, а обращаются к нему извне сугубо по публичному интерфейсу. Та в общем стандартный паттерн, всякие хвабрики так и делают.

2) Уже интереснее.
Скомпилится. Далее, согласно контракту, дистинкт операция нетерминальная и ленивая, поэтому... если с конкретной реализацией не нахомутано, то в итоге из нее выйдет новый стрим из одной единички, который будет залимитирован до не более 5, то есть получится новый же стрим из одной единички, которая будет выведена на екран.

3) Интересно.
Так.
Как как бэ намекает нам гугл, сплитератор эррэй листа — лэйт-биндинг (и сайзд). Значит, биндинг будет на фёст траверс, ну или запрос о размере. Таким образом, на время попытки модификации источника, биндинг уже состоялся, и, таким образом, должна вывалиться ConcurrentModificationException.
Ну а скомпилиться, по идее, скомпилится.

ЗЫ Дисклеймер: со стримами не работал.
Да и джавы я не знаю.

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

тогда дважды, справа и слева

но то такое, вы скажите лучше — правильно чи не?

точнее, я вижу уже что первое не, я чего-то не туда посмотрел, решил что Stream предок IntStream — а это не так, как указал джентльмен выше в каментах

а остальное?

Здесь самое интересное, что IntStream и Stream имеют общего предка — BaseStream, но сам IntStream от Stream не наследуется.

Во втором примере будет сгенерировано два уникальных значения — 0 и 1, которые и будут выведены. Но limit будет ждать еще три элемента, которых никогда не появится, поэтому этот код никогда не завершится.

а вы это проверяли?
или все еще в умозрительном режиме? :-)

да, и как насчет третьего?

yep

потому компилер выкинет такой код на мороз с ошибкой

щазз... ми ж явно тип приводимо — чого це він нам буде опиратися :)
потім, в рантаймі буде помилка.

Кстати, стрим апи лучше не юзать если метод очень часто вызывается, особенно filter и collect т.к создают большую нагрузку на gc. Буквально вчера при лоад тесте словил OOM в месте где было два filter подряд.

Буквально вчера при лоад тесте словил OOM в месте где было два filter подряд.
А подробнее можно? За счет чего происходит переиспользование памяти?

Создаются обьекты вроме spliterator, ReferencePipeline, StatelessOp, ChainedReference, CollectorImpl, те же лямбды тоже создаются как обьекты если референсят внешний контекст. В методе с навороченной, который часто вызывается создавалась куча лишних обьектов под капотом streams api.

Создаются обьекты вроме spliterator, ReferencePipeline, ...
Но-но-но. Эти объекты довольно легкие, и если у вас не «числодробилка» то тут не на чем «течь», один «бизнес объект», часто, выедает больше чем пайплайн на около 5 операций.
Проблема может быть если у вас «вложенные циклы».
Я не знаю вашу задачу, поэтому и попросил описать подробнее, ибо утечку на объектах пайплайна мне сложно представить.
4000 — 6000, с несколькими .filter вызовами. Метод вызвался в нескольких потоках, раз в 200 мс.
А вот тут мне не нравятся слова «потоки» и «200 мс».
1) У вас по бизнесс логике шарятся стримы между потоками? Что именно занимает 200 мс?
2) Или вы поймали ООМ на самопальном микробенчмарке?

Я думаю, что скорее всего стримы не шарятся между потоками, а просто методы, в которых создаются стримы, вызываются из разных потоков.

и если у вас не «числодробилка»
Числодробилка.
утечку на объектах пайплайна мне сложно представить.
Утечки собственно и нет, есть лишний оверхед чисто от самого stream api.
А вот тут мне не нравятся слова «потоки» и «200 мс». 1) У вас по бизнесс логике шарятся стримы между потоками? Что именно занимает 200 мс?
Несколько потоков каждые 200 мс (у каждого отдельный single threaded ScheduledExecuterService) вызывают метод в котором обрабатывается коллекция размером от 4000 до 6000 обьектов (обьекты не пересоздаются, т.е. более менее статические и преаллоцированны). Стримы не шарятся.

ООМ словили на тестовом сервере, никакими бенчмарками и не пахло. По сути, из за чистого оверхеда от Stream Api. Ошибка была не java heap space, а java.lang.OutOfMemoryError: GC overhead limit exceeded error. VisualVM собственно покзал, что создается достаточно много обьектов из внутренней реализации стримов.

Числодробилка.
Тут законы логики не применимы :)
.
Следующее будет сказано не с целью «наехать»:
Утечки собственно и нет, есть лишний оверхед чисто от самого stream api.
Ошибка была не java heap space, а java.lang.OutOfMemoryError: GC overhead limit exceeded error.
Сугубо из моего опыта + я не видел ваши мемдампы, то есть гадаю по фотографии :)
ГЦ оверхед — это как раз утечка в чистом виде, у вас просто не чистится память. Мне сложно представить чтобы у стримов был большой retained size. Единственный вариант который, с ходу, приходит в голову — это то что у вас обработка одного «запроса» __сильно__ больше чем 200 мс, но тогда скорее всего должны быть более тяжелые объекты чем стримовые.
VisualVM собственно покзал, что создается достаточно много обьектов из внутренней реализации стримов.
МАТ как-то по веселее, на мой вкус.

В любом случае, старый добрый for loop таких проблем не имеет.

В любом случае, старый добрый for loop таких проблем не имеет.
1) А итератор? Стримы точно так же дают О(1), вроде как.
2) Стримы — это про читабельность и немного про инкапсуляцию.

Я думаю, тут еще зависит от того, сколько элементов в стриме. Сколько у вас было?

4000 — 6000, с несколькими .filter вызовами. Метод вызвался в нескольких потоках, раз в 200 мс.

А в чем заключается паззловость этих примеров?

Андрей, а что для вас паззл?

Кто ходит не в Блокноте — на семинар может не приходить.

Вы знаете, Алексей, тема паззлов довольна популярна. Я думаю, что те, кто приходят на JEEConf, точно не в блокноте кодят, но тем не менее паззлам посвящаются отдельные доклады: jeeconf.com/...izarre-and-the-wonderful

Это доклад, который еще будет, а вот доклад прошлого года: www.youtube.com/watch?v=H_q14bzBOIk

Ну судя по 86 «лайкам» посмотревших, доклад все же понравился

За пазлы в боевом коде в нормальных компаниях увольняют. А пазлы переписывают.

А я и не призываю их использовать в боевом коде. Это был пост для разрядки мозга.

Для разрядки мозга применяются гораздо более правильно составленные пазлы, которые КАЖУТСЯ удобочитаемыми, но на деле таковыми не являются. А задачи из серии «что ответит компилятор» практической цели не имеют.

Отлично. Попробуйте придумать тогда "

гораздо более правильно составленные пазлы
". Я думаю, все с удовольствием их посмотрят.
тема паззлов довольна популярна.
среди докладчиков)

Пазлы — это вторичный продукт работы программиста. Какие-то кастомные случаи, которых миллионы. Это то с чем программист сталкивается каждый день решая ОСНОВНУЮ задачу. И пазлы — это то в чем программист хочет разобраться и поскорее забыть, что-бы продолжать работать над основной задачей. А на конференциях эти побочные продукты оформляются в виде доклада. Мне например не очень интересно слушать о том, с чем я врядли столкнусь а если и столкнусь, то вряд-ли из доклала вспомню, так как не хочется запоминать мелочи. На конференциях гараздо интересней слушать об каких-то трендах, идеях, архитектурных вещах, чем о разных случаях из опыта, которых и у меня на работе своих хватает.
Для докладчика легче всего сделать доклад на пазлах — надо просто вспомнить любой деень из работы. «Cижу я как-то дебажу проблему. И тут оказалось, что в библиотеке Х в классе Z при событиях А B C возникает ексепшн — о вынесу ка я это в пазл». Но аудитории это не очень то интересно.

Если аудитории это не очень интересно, почему люди приходят на такие доклады? Вот Егор Бугаенко например провел доклад по OOP паззлам jug.ua/...017/02/oop-java-puzzlers

Если аудитории это не очень интересно, почему люди приходят на такие доклады?
Не «аудитории», а «профессиональным программистам». Если вы не заметили, то аудитория наших конференций состоит __по большей части__ из «девочек-тинейджеров», а не инженеров (Существование Егора Бугаенко, как спикера, только подтверждает это).

Ходят в основном джуны и просто для «а поговорить»

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

Александр, полностью согласен с вами.

Тема пазлов вызывает желание while true пролить 1 грамм кипятка на штаны говнокодера.

Пазлы в реальном боевом коде недопустимы. Учитывая КОЛИЧЕСТВО реального кода, с которым приходится иметь дело, ЧИТАЕМОСТЬ и ДОКУМЕНТИРВАННОСТЬ — едва ли не основные параметры качества. Если код просто работает — это даже не на троечку, это на два с плюсом. Потому что код без багов — редкость. А код написанный пазлами и без багов может сделать разве только обфускатор, да и то с грабельками которые позже обязательно вылезут.

Простой пример: чему равно !1 ?
Мой ответ — выдать ровно такую зарплату тому, кто это напишет в код.

Подписаться на комментарии