Я давно и прочно ничего хорошего от Sun Microsystems Oracle не жду. Я помню странный выход Java 6 (когда ничего особого в язык не добавили), зарезанную Java 7 (когда столько всего наобещали и практически ничего не сделали). Здесь нужно сделать отступление - Java она (или он, как кому нравится) едина в двух лицах. Это платформа (AKA JVM) которая развивается семимильными шагами и в которой столько всего нового, вкусного и классного появилось, что и говорить об этом не стоит (по крайней мере в рамках этой статьи). Но Java это и язык программирования. А вот он, скажем так, со времен Java 5 особо не менялся. И это за 8 (ВОСЕМЬ) лет, если считать с выхода Java 6 (а если с Java 5 то и за все 11).
Но хорош ныть :). Мы перешли на Java 8 еще в начале этого года, но порелигиозным принципам некоторым причинам я ее особо руками не трогал. Ну и в принципе зачем!? Хотите функциональщины - есть Scala, хотите скобочек Lisp'a - есть Clojure (да-да мы все это используем).
А тут мне понадобилось странного. Настолько странного, что делать это нужно именно в Java и считать много всякого и не особо приятного (речь идет о группировках, суммах, средних значениях и прочих агрегатах, которые нужно считать по набору значений). Все это желательно делать быстро (в несколько потоков и пр.). Делать быстро я умел, а вот считать агрегаты на Java это скажем так ... "не фонтан". Причем для меня это насколько "не фонтан", что потом хочется пойти руки вымыть. О чем я? Ну, представьте, что у вас есть List<Map<String, Object>> где в Object валяется что попало от Integer до BigDecimal, но может и null придти и вот по ключу это все нужно как-то считать. Узнаете?
Ну, да мы это все можем обернуть в какие-то методы (я про cast'ы и проверки на null) , но как в том анекдоте - "осадочек" остается.
Мне понадобилось считать не просто суммы, а с разбивкой по каким-то категориям. В примере она одна, но у меня их было обычно 2-4, что еще добавляет "вкуса" в этот код: А теперь представьте, что мне нужно примерно 30 таких итоговых значений (к счастью только сумм и количеств) по пяти наборам категорий разбивки, у каждого набора значений своя фильтрация и пр.
И тут нам на помощь приходят Stream'ы из Java 8. Я имел небольшой опыт решения подобных задач на Scala и мне показалось, что тот же подход можно использовать и здесь - разбивку по группам с обработкой каждой группы индивидуально (для тех кто знаком с SQL - по сути Collectors.groupingBy() - это и есть Ваши любимые SQL'ные GROUP BY).
Что из этого получилось?
Первая задача - oneliner: Вторая чуть похитрее, но не шибко: Здесь стоит отметить следующее:
И напоследок - настолько сильных чувств как при работе с Java 8 Stream'ами (и не только) я не испытывал, пожалуй, с средины 90х когда портировал какую-то систему на dBase'e на тот диалект SQL'a с двухуровневого подхода (это когда вы "ручками" в цикле проходите по всем записям и что-то считаете и куда-то складываете). Очень похожие впечатления.
С уверенностью могу сказать, что Java 8 -it's a really big deal пожалуй наибольшие изменения в языке которые я вижу. Команда Oracle - вы молодцы! (никогда не думал что это напишу :)
P.S. Ложка дегтя по Stream'ам пока единственный "косяк", который увидел - нельзя получить последний элемент stream'a. Я понимаю, что Stream может быть бесконечным (я про нечто сродни thunk), но все равно иногда хочется, а то код выглядит несколько странно:
Но хорош ныть :). Мы перешли на Java 8 еще в начале этого года, но по
А тут мне понадобилось странного. Настолько странного, что делать это нужно именно в Java и считать много всякого и не особо приятного (речь идет о группировках, суммах, средних значениях и прочих агрегатах, которые нужно считать по набору значений). Все это желательно делать быстро (в несколько потоков и пр.). Делать быстро я умел, а вот считать агрегаты на Java это скажем так ... "не фонтан". Причем для меня это насколько "не фонтан", что потом хочется пойти руки вымыть. О чем я? Ну, представьте, что у вас есть List<Map<String, Object>> где в Object валяется что попало от Integer до BigDecimal, но может и null придти и вот по ключу это все нужно как-то считать. Узнаете?
Ну, да мы это все можем обернуть в какие-то методы (я про cast'ы и проверки на null) , но как в том анекдоте - "осадочек" остается.
Мне понадобилось считать не просто суммы, а с разбивкой по каким-то категориям. В примере она одна, но у меня их было обычно 2-4, что еще добавляет "вкуса" в этот код: А теперь представьте, что мне нужно примерно 30 таких итоговых значений (к счастью только сумм и количеств) по пяти наборам категорий разбивки, у каждого набора значений своя фильтрация и пр.
И тут нам на помощь приходят Stream'ы из Java 8. Я имел небольшой опыт решения подобных задач на Scala и мне показалось, что тот же подход можно использовать и здесь - разбивку по группам с обработкой каждой группы индивидуально (для тех кто знаком с SQL - по сути Collectors.groupingBy() - это и есть Ваши любимые SQL'ные GROUP BY).
Что из этого получилось?
Первая задача - oneliner: Вторая чуть похитрее, но не шибко: Здесь стоит отметить следующее:
- Collectors.groupingBy() принимает на вход List<T>. В моем случае ArrayList<Object> - его, конечно, можно создавать "руками", но лучше всего это делает Guava (точнее я не знаю как "красиво" это сделать на Java 8 API). Так, что не обессудьте Lists.newArrayList() - это именно Guava. Коллеги, меня конечно "заплюют" за этот код и скажут, что
кошерноправильно только ImmutableList.of() но в данном случае это не принципиально, а первый вызов мне кажется более очевидным. - Почему-то в Collectors.groupingBy() не работает type inference. По-этому пришлось написать явный cast (я про (Map<String, Object> pData)).
- Collectors.groupingBy() возвращает Map<ArrayList<Object>, List<Map<String, Object>>>(естественно, это не generic, а мой конкретный случай). Обратите внимание на ключ ArrayList<Object>. Это не проблема, так как List реализует и equals(), и hashCode().
- В данной задаче меня совсем не интересуют ключи по которым делается разбивка. Мне их гораздо проще потом будет брать из данных, поэтому я сразу получаю Collection<List<Map<String, Object>>>.
- Императивная обработка полученных в предыдущем пункте данных не противоречит моим религиозным убеждениям, а наоборот кажется очевидной и "нативной" что ли (я про for(List<Map<String, Object>> item : groups) если кто-то еще не понял).
- Обратите внимание, что в этом месте задача свелась к предыдущей :)
И напоследок - настолько сильных чувств как при работе с Java 8 Stream'ами (и не только) я не испытывал, пожалуй, с средины 90х когда портировал какую-то систему на dBase'e на тот диалект SQL'a с двухуровневого подхода (это когда вы "ручками" в цикле проходите по всем записям и что-то считаете и куда-то складываете). Очень похожие впечатления.
С уверенностью могу сказать, что Java 8 -
P.S. Ложка дегтя по Stream'ам пока единственный "косяк", который увидел - нельзя получить последний элемент stream'a. Я понимаю, что Stream может быть бесконечным (я про нечто сродни thunk), но все равно иногда хочется, а то код выглядит несколько странно:
Комментариев нет:
Отправить комментарий