Comments 94
Сам еще не перешел на Java 8, но глядя на это все стало весьма интересно: а кто-нибудь делал замеры производительности Stream API vs. for/foreach. И есть ли какие-то общие рекомендации когда и что лучше использовать.
Совсем недавно было: habrahabr.ru/post/255813
Вот тут есть кое-какое сравнение
Итерация через Stream API задействует все ядра многоядерной системы, for/foreach нет.
Без вызова parallel() тоже?
Stream API имеет один недостаток — там для всех streams используется один и тот же пул так что если у вас в программе много много поточной обработки то могут быть очень странные побочные эффекты
А пример можно? Хотя бы гипотетический? Т.е. в чём будет заключаться возможные проблемы?
Ну пример вполне не гипотетический. Если все все программисты в мире понимают что ForkJoin пул можно использовать только для легковесных коротких операций то ОК.
Но предположим я хочу использовать stream API (оно же так здорово заменяет циклы) и для каждого элемента списка скажем сделать запрос в БД или к какому-то третьестороннему API. В общем-то это не такой уж редкий случай.
Логично использовать parallel stream чтобы выполнить всю связку запросов быстрее.
Но если не знать о том что пул один на все-все приложение, то пока выполняются эти запросы все остальные parallel streams будут стоять и ждать.
Причем это будет достаточно «весело» в одном месте приложения невинные операции будут занимать иногда 10 мс, а иногда 10,000 мс.
Опять же если все программисты в вашем проекте и все разработчики библиотек используют parallel streams правильным образом — все хорошо, но достаточно попасться одному джуниору недочившему доки и все пойдет криво.
Но предположим я хочу использовать stream API (оно же так здорово заменяет циклы) и для каждого элемента списка скажем сделать запрос в БД или к какому-то третьестороннему API. В общем-то это не такой уж редкий случай.
Логично использовать parallel stream чтобы выполнить всю связку запросов быстрее.
Но если не знать о том что пул один на все-все приложение, то пока выполняются эти запросы все остальные parallel streams будут стоять и ждать.
Причем это будет достаточно «весело» в одном месте приложения невинные операции будут занимать иногда 10 мс, а иногда 10,000 мс.
Опять же если все программисты в вашем проекте и все разработчики библиотек используют parallel streams правильным образом — все хорошо, но достаточно попасться одному джуниору недочившему доки и все пойдет криво.
Да, после scala оно выглядит довольно жалко. Нет в мире совершенства.
Надо отдать должное и Java 8: ParView в Scala так и нет, тут Stream API впереди.
Видимо, исходили из разных задач. В java 8 steam api заточен, в том числе, под параллельную обработку. В scala на это упора не было, исходно шли в сторону элементов функциональщины, поэтому map/flatMap/filter есть у многих стандартных типов.
А по поводу «жалко» я имею ввиду следующие неудобства:
— куча статических импортов, без которых с использованием stream api код перестаёт нормально выглядеть (конструкции слишком громоздкие, взгляд начинает цепляться за всякие Collectors.toList()),
— отсутствие синтаксического сахара для apply приводит к более громоздким конструкциям,
— проблемы с оборачиванием функций, которые могут выбрасывать checked exceptions
— отсутствие операций foldL/foldR,
Разная нотация для вызова метода и передачи «ссылки» на него — просто особенность, в java получается явное представление (типа method1().orElseGet(this::method2())), а в scala — более удобное, но менее явное (если бы интерфейс Option был бы такой же, то что-то вида method1.orElseGet(method2)).
Кроме того, пример из статьи getFirstJavaArticle().orElseGet(this::fetchLatestArticle), если по заветам автора fetchLatestArticle также возвращает Optional, как и getFirstJavaArticle не скомпилируется, т. к. this::fetchLatestArticle будет иметь тип
А по поводу «жалко» я имею ввиду следующие неудобства:
— куча статических импортов, без которых с использованием stream api код перестаёт нормально выглядеть (конструкции слишком громоздкие, взгляд начинает цепляться за всякие Collectors.toList()),
— отсутствие синтаксического сахара для apply приводит к более громоздким конструкциям,
— проблемы с оборачиванием функций, которые могут выбрасывать checked exceptions
— отсутствие операций foldL/foldR,
Разная нотация для вызова метода и передачи «ссылки» на него — просто особенность, в java получается явное представление (типа method1().orElseGet(this::method2())), а в scala — более удобное, но менее явное (если бы интерфейс Option был бы такой же, то что-то вида method1.orElseGet(method2)).
Кроме того, пример из статьи getFirstJavaArticle().orElseGet(this::fetchLatestArticle), если по заветам автора fetchLatestArticle также возвращает Optional, как и getFirstJavaArticle не скомпилируется, т. к. this::fetchLatestArticle будет иметь тип
Supplier<Optional<Article>>
, а не Supplier<Article>
.куча статических импортов, без которых с использованием stream api код перестаёт нормально выглядеть (конструкции слишком громоздкие, взгляд начинает цепляться за всякие Collectors.toList())
Это я в некоторой мере починил. Если есть дополнительные пожелания к либе, готов выслушать.
— отсутствие синтаксического сахара для apply приводит к более громоздким конструкциям,
Можно пример на эту тему?
foldR, foldL, насколько я понимаю, антипараллельны. Концепция стримов как раз была в том, чтобы ограничиться операциями, которые нормально параллелятся. По этой же причине zip убрали. Можно, кстати, пример кода, когда с foldR/foldL всё красиво, а со stream api плохо?
Ссылки на методы — следствие изначального дизайна Java. Имена классов, имена переменных/полей и имена методов — это три разных пространства имён, и из кода должно быть ясно, что имеется в виду. У вас может быть поле fetchLatestArticle типа Supplier<Article> и при этом метод fetchLatestArticle(), который возвращает Article. Тут и так сделали отступление, смешав классы и поля. Например foo::bar — это может быть статический метод bar в классе foo, а может быть метод bar у поля foo. Видимо, решили пойти на такой шаг, потому что классы обычно с большой буквы, а поля с маленькой.
getFirstJavaArticle().orElseGet(this::fetchLatestArticle)
Это misuse. Здесь вы никак не специфицируете, что сделать, если fetchLatestArticle вернёт пустой optional. Надо написать что-то типа
getFirstJavaArticle().orElseGet(() -> fetchLatestArticle().get())
или getFirstJavaArticle().orElseGet(() -> fetchLatestArticle().orElse(null))
или ещё что-нибудь. Спецификация обработки ошибки может быть разной, и надо её явно указать.Кажется, понял, что вы имели в виду про apply. Java хранит ценное качество, что для понимания написанного требуется довольно узкий контекст. Благодаря этому тут не так много синтаксического сахара, как в других языках. Если перед круглыми скобками стоит идентификатор, это значит, что идентификатор — метод, а не десяток разных вещей в зависимости от контекста, как в некоторых других языках. Это очень хорошая фича, чтобы от неё взять и отказаться. Поэтому и нельзя написать
basedOnTag("Java")
. Это бы позволило писать код, где уже непонятно, что перед нами — объект или метод (в данном случае basedOnTag — объект). Ну и плюс та же проблема с пространствами имён: в текущем классе может быть метод с именем basedOnTag. Может, мы хотим его вызвать?Ответил здесь: habrahabr.ru/post/256057/#comment_8389073
foldR, foldL, насколько я понимаю, антипараллельны. Концепция стримов как раз была в том, чтобы ограничиться операциями, которые нормально параллелятся. По этой же причине zip убрали. Можно, кстати, пример кода, когда с foldR/foldL всё красиво, а со stream api плохо?Да, они не могут выполняться параллельно, т. к. протаскивают состояния аккумулятора.
Примеров для сравнения stream api и foldR/foldL не приведу, у меня нет кода со stream api (пока использую преимущественно java 7, местами ещё java 6). Но озвучу один важный момент: когда мы сравниваем stream api и foldR/foldL, то сравниваем их в соответствующих контекстах (java8 и scala), что убивает прямое сравнение на корню.
Когда мы сравнивали, то важным моментом был существенно более слабый вывод типов в java8, отсутствия в scala checked exceptions, что сильно сказывается на удобстве использования. Плюс, в случае scala часто бывает удобно тащить tuple с данными, что в java требует использования отдельных контейнеров (например, Map<K, V>.Entry). Или, например, более удобное преобразование Map[K, V] <-> List[(K, V)]. С одной стороны мелочи, но из них складывается общее удобство использования.
Что же до использования foldR/foldL, то я предпочитаю явную tail-recursive функцию вместо использования foldL/foldR, когда это возможно: оно нагляднее и проще для понимания.
Кажется, понял, что вы имели в виду про apply. Java хранит ценное качество, что для понимания написанного требуется довольно узкий контекст. Благодаря этому тут не так много синтаксического сахара, как в других языках. Если перед круглыми скобками стоит идентификатор, это значит, что идентификатор — метод, а не десяток разных вещей в зависимости от контекста, как в некоторых других языках. Это очень хорошая фича, чтобы от неё взять и отказаться. Поэтому и нельзя написать basedOnTag(«Java»). Это бы позволило писать код, где уже непонятно, что перед нами — объект или метод (в данном случае basedOnTag — объект). Ну и плюс та же проблема с пространствами имён: в текущем классе может быть метод с именем basedOnTag. Может, мы хотим его вызвать?Да, это и плюс (с точки зрения понимания человеком, удобства разбора и анализа), и минус (больше синтаксического шума, меньше гибкость языка).
Если смотреть дальше, то можно уйти в сторону языков типа smalltalk, ruby, python, где этот контекст не выводится статически (во время компиляции), но разрешается во время исполнения. Больше гибкости, больше метапрограммирования, больше шансов для выстрела в ногу.
в java требует использования отдельных контейнеров (например, Map<K, V>.Entry). Или, например, более удобное преобразование Map[K, V] <-> List[(K, V)]
Что-то подобное я прикрутил в моём классе EntryStream: там можно тащить поток Entry, но при этом выполнять операции (map, filter и т. д.) только на ключах или только на значениях. Получается примерно такой код:
public List<Sequence> calculate(Map<String, List<ChipSeqPeak>> chromosomeToPeaks, GenomeDatabase db, int minLength) {
return EntryStream.of(chromosomeToPeaks)
.mapKeys( chr -> db.fetchSequence(chr) )
.flatMapValues( List::stream )
.mapKeyValue( (sequence, peak) -> peak.getSequenceRegion(sequence, minLength))
.toList();
}
Приятнее, чем было бы на голых стримах.
Если смотреть дальше, то можно уйти в сторону языков типа smalltalk, ruby, python, где этот контекст не выводится статически (во время компиляции), но разрешается во время исполнения.
Ну вот в том-то и дело, что у Java свой путь и своя философия, и статическое разрешение — часть её. Зачем создавать ещё один smalltalk или ruby, когда оно уже есть?
Приятнее, чем было бы на голых стримах.Я видел ваш пост про streamex, выглядит куда более удобоваримо, нежели голые стримы.
Ну вот в том-то и дело, что у Java свой путь и своя философия, и статическое разрешение — часть её. Зачем создавать ещё один smalltalk или ruby, когда оно уже есть?Незачем. Никто и не агитирует, вроде.
Закоммитил, кстати, foldLeft. Подумаю ещё, потестирую, но наверно пусть будет. Теоретически он может даже что-то выиграть с параллельности, если, например, операция в foldLeft долгая и перед ней долгий map. Но в целом если можно использовать reduce, то стоит это делать.
Вот с foldRight засада: ему требуется память на весь поток. В Scala то же самое. Вон люди пишут, как выстрелить себе в ногу. Вдобавок пока предыдущие операции потока не выполнены, foldRight даже начать нельзя, то есть тут параллельность убивается полностью. То есть реализация foldRight должна быть вроде
Причём второй поток должен выполняться в том же пуле, что и первый. Пока добавлять не буду.
На примитивных потоках foldLeft добавить можно, но там другая проблема: нет стандартного функционального интерфейса для функции, принимающей примитив и объект и возвращающей объект. Можно добавить такой интерфейс, но пока не хочется. Если кому-то сильно приспичит, пусть boxed() вызывают.
Вот с foldRight засада: ему требуется память на весь поток. В Scala то же самое. Вон люди пишут, как выстрелить себе в ногу. Вдобавок пока предыдущие операции потока не выполнены, foldRight даже начать нельзя, то есть тут параллельность убивается полностью. То есть реализация foldRight должна быть вроде
List<T> list = toList(); IntStreamEx.range(list.size()).map(i -> list.get(list.size()-1-i)).foldLeft(...)
Причём второй поток должен выполняться в том же пуле, что и первый. Пока добавлять не буду.
На примитивных потоках foldLeft добавить можно, но там другая проблема: нет стандартного функционального интерфейса для функции, принимающей примитив и объект и возвращающей объект. Можно добавить такой интерфейс, но пока не хочется. Если кому-то сильно приспичит, пусть boxed() вызывают.
В Scala с foldRight проблема не на всех коллекциях. На индексируемой ленивой дополнительна память не требуется. Ну а с параллельностью и foldLeft не дружит.
Ну дело в том, что в Java потоки — это вообще не коллекции. А с параллельностью foldLeft чуть-чуть дружит. Например, такой бенчмарк:
Результаты:
Пока выполняется текущий шаг foldLeft, другой поток выполняет следующий map, из-за чего параллельная версия всё равно может быть быстрее. Но не быстрее, чем суммарное время выполнения всех шагов foldLeft, конечно.
private void sleep() {
try { Thread.sleep(10); } catch(InterruptedException e) {}
}
@Benchmark
public void foldLeftSeq(Blackhole bh) {
bh.consume(IntStreamEx.range(0,10).boxed().map(x -> {sleep();return x;})
.foldLeft(0, (a, b) -> {sleep();return a+b;}));
}
@Benchmark
public void foldLeftPar(Blackhole bh) {
bh.consume(IntStreamEx.range(0,10).parallel().boxed().map(x -> {sleep();return x;})
.foldLeft(0, (a, b) -> {sleep();return a+b;}));
}
Результаты:
Benchmark Mode Cnt Score Error Units
FoldLeft.foldLeftPar avgt 20 126046361,977 ± 2005404,512 ns/op
FoldLeft.foldLeftSeq avgt 20 199882975,012 ± 16028,386 ns/op
Пока выполняется текущий шаг foldLeft, другой поток выполняет следующий map, из-за чего параллельная версия всё равно может быть быстрее. Но не быстрее, чем суммарное время выполнения всех шагов foldLeft, конечно.
В этом отношении foldLeft от foldRight не должен отличаться в общем случае. Они вообще близнецы и разница только в конкретных реализациях. Стримы — не коллекции, несомненно, но они обладают информацией об исходной коллекции и для индексируемых коллеций left и right — условности.
Ну вот в сорцах-то видно, что ни разу они не близнецы даже для линейных последовательностей с быстрым случайным доступом. Реализация foldLeft — обычный императивный цикл, а foldRight — рекурсия, причём не хвостовая. По ссылке, что я дал выше, как раз сравнивается
И получается, что они эквивалентны, но при этом второй вариант стек не жрёт.
С точки зрения стримов индексируемая коллекция — исключительно частный случай. Коллекция может быть конкатенацией нескольких коллекций, может быть результатом flatMap, может прийти после filter+limit, может вообще прийти из файла или из сокета. Left и right — далеко не условности. Left гораздо проще, чем right. У стримов, например, даже операции reverse нету.
list.foldRight("X")((a,b) => a + b)
list.reverse.foldLeft("X")((b,a) => a + b)
И получается, что они эквивалентны, но при этом второй вариант стек не жрёт.
С точки зрения стримов индексируемая коллекция — исключительно частный случай. Коллекция может быть конкатенацией нескольких коллекций, может быть результатом flatMap, может прийти после filter+limit, может вообще прийти из файла или из сокета. Left и right — далеко не условности. Left гораздо проще, чем right. У стримов, например, даже операции reverse нету.
Смотря в каких сорцах — я же несколько раз написал, что зависит от импелментации.
Далее: мы говорили про параллельность, а это значит, что кроме прочего исходная коллекция должна быть разбиваемой на части, иначе накладные расходы могут сожрать все преимущества. В той же Scala collection.par для индексируемой коллекции создает обертку, а для не индексируемой — копирует в индексируемую.
Раз мы говорили про параллельные вычисления, то folrLeft может начать работу после завершения обработки первого чанка, foldRight — после последнего. Причем «первый» и «последний» здесь — не про порядок выполнения, ничто не мешает последнему выполниться раньше и даже, при определенных усилиях при реализации, первым начать выполняться.
Кстати: 10 элементов и слип — это крайне странный способ тестирования параллельных вычислоений.
Далее: мы говорили про параллельность, а это значит, что кроме прочего исходная коллекция должна быть разбиваемой на части, иначе накладные расходы могут сожрать все преимущества. В той же Scala collection.par для индексируемой коллекции создает обертку, а для не индексируемой — копирует в индексируемую.
Раз мы говорили про параллельные вычисления, то folrLeft может начать работу после завершения обработки первого чанка, foldRight — после последнего. Причем «первый» и «последний» здесь — не про порядок выполнения, ничто не мешает последнему выполниться раньше и даже, при определенных усилиях при реализации, первым начать выполняться.
Кстати: 10 элементов и слип — это крайне странный способ тестирования параллельных вычислоений.
Да уж. И странно, что Haskell ещё не упомянули в комментариях…
Хм, увидел название статьи, думал, будет что-то новое, оригинальное. Прочитал статью, не увидел ничего нового. Потом подумал: «ага, это же перевод; тогда, наверное, оригинал написан весной 2014: именно тогда был бум простых примеров по Java 8». Ан нет, статья свежак — 13 апреля 2015 года.
В любом случае, спасибо за проделанную работу!
В любом случае, спасибо за проделанную работу!
Довольно круто, правда?
Как C#-щик хочу поздравить всех Java-истов с разморозкой!
А какая разница на чем вы пишете? Поздравлять вроде можно и без указания собственной принадлежности.
Он тонко намекал на это: en.wikipedia.org/wiki/Language_Integrated_Query
UFO just landed and posted this here
Вы сравниваете совсем разные вещи.
Как минимум используя Linq можно писать запросы и к удаленному хранилищу и к любой локальной структуре данных в едином виде. Это очень полезно.
Jooq и querydsl — это возможность строить запросы к базе.
Тк я работал только с Jooq, то могу сказать только за него — синтаксис менее понятный чем у Linq.
Ну и с linq вся фишка в поддержке рантайма, а именно в том что вы можете получить доступ к AST лямбд которые вы передаете в Linq методы. Это позволяет писать куда более простой и читабельный код.
Как минимум используя Linq можно писать запросы и к удаленному хранилищу и к любой локальной структуре данных в едином виде. Это очень полезно.
Jooq и querydsl — это возможность строить запросы к базе.
Тк я работал только с Jooq, то могу сказать только за него — синтаксис менее понятный чем у Linq.
Ну и с linq вся фишка в поддержке рантайма, а именно в том что вы можете получить доступ к AST лямбд которые вы передаете в Linq методы. Это позволяет писать куда более простой и читабельный код.
Эээм, то есть теперь каждый человек который пишет на языке где поддерживается project/filter/fold может прийти и ехидно «поздравить с разморозкой»? Это детский говнизм от которого коробит, а никакой не намёк.
На C# своих заморочек хватает. Например, он очень медленно развивается. Идей много бродит, но добавляют очень медленно. Roslyn долгострой. XAML излишне тяжелый. Да и понять MS просто — они остерегаются резко увеличить порог вхождения в технологию.
LINQ — 2007г. А тут Java8 получила наконец-то! Дожили. 8 лет прошло. Я могу только порадоваться за Java разработчиков.
Мир очень быстро меняется и хочется много и прямо сейчас. Бывает и с C# народ уходит на новые языки.
Вот например, новое в C# 6.0 (синтаксический сахар, но приятный):
public double Sqr(double x) => x*x;
вместо
public double Sqr(double x) { return x*x; }
Еще добавили инициализацию свойств:
public int MyProp {get;set;} = 10;
Но вот сделали бы сразу:
public int MyProp {get => 2*x; set => x = value/2;} = 10; // с запуском сеттера
Впрочем, я не настолько прокаченный Computer Scientist. Может где и недосмотрел противоречия.
LINQ — 2007г. А тут Java8 получила наконец-то! Дожили. 8 лет прошло. Я могу только порадоваться за Java разработчиков.
Мир очень быстро меняется и хочется много и прямо сейчас. Бывает и с C# народ уходит на новые языки.
Вот например, новое в C# 6.0 (синтаксический сахар, но приятный):
public double Sqr(double x) => x*x;
вместо
public double Sqr(double x) { return x*x; }
Еще добавили инициализацию свойств:
public int MyProp {get;set;} = 10;
Но вот сделали бы сразу:
public int MyProp {get => 2*x; set => x = value/2;} = 10; // с запуском сеттера
Впрочем, я не настолько прокаченный Computer Scientist. Может где и недосмотрел противоречия.
del
Мне кажется, что вот это уже лишнее: basedOnTag.apply(«Java»).
Тут бы стоило сделать обычный метод, который возвращает предикат по тэгу. А то вот это вот apply выглядит достаточно уродлив. А было бы просто predicateByTag(«Java»).
Тут бы стоило сделать обычный метод, который возвращает предикат по тэгу. А то вот это вот apply выглядит достаточно уродлив. А было бы просто predicateByTag(«Java»).
Примеры притянутые так за уши так что уши оторвались от головы.
В первом случаи читабильность упала просто на порядок. Или сделайте проверку на «Java» AND/OR «Basic». Я считаю что ваш пример зарефакторится сразу в классический вариант.
Во втором проверка на null это способ защиты от сбоев/ошибок в программе. А как известно 50 процентов работы любой программы это обработка ошибок. В вашем варианте все придется обрабатывать в исключениях и блок исключений у вас лопнет от объема кода.
В 3-ем примере с принтером вообще глупость. Принтер это объект и объект сам должен знать и уметь обрабатывать свое состояние.
В первом случаи читабильность упала просто на порядок. Или сделайте проверку на «Java» AND/OR «Basic». Я считаю что ваш пример зарефакторится сразу в классический вариант.
Во втором проверка на null это способ защиты от сбоев/ошибок в программе. А как известно 50 процентов работы любой программы это обработка ошибок. В вашем варианте все придется обрабатывать в исключениях и блок исключений у вас лопнет от объема кода.
В 3-ем примере с принтером вообще глупость. Принтер это объект и объект сам должен знать и уметь обрабатывать свое состояние.
UFO just landed and posted this here
Ну вообще-то это движуха в сторону FP.
Так что ваш ответ тоже за уши притянут.
В первом случае читабельность упала только у тех кто, извините, «в танке».
Ведь это самый простой случай. Как только добавится пару условий и пару функций обработчиков, классический Java код превратится в жуткий, километровый бойлерплейт.
Во втором случае от null никуда не уходят, проверки не убираются. Optional просто позволяет удобно работать с ними, избавляя от километров тупого java бойлерплейта, оборачивая самые простые случаи (коих большинство в таких проверках) в удобную, компактную конструкцию.
В третьем это не глупость, это называется функциональный подход, когда разделяют состояние и работу с ним.
Почитайте про чистые функции и вообще про принципы ФП что ли.
Хотя это далеко не самый лучший пример конечно.
Так что ваш ответ тоже за уши притянут.
В первом случае читабельность упала только у тех кто, извините, «в танке».
Ведь это самый простой случай. Как только добавится пару условий и пару функций обработчиков, классический Java код превратится в жуткий, километровый бойлерплейт.
Во втором случае от null никуда не уходят, проверки не убираются. Optional просто позволяет удобно работать с ними, избавляя от километров тупого java бойлерплейта, оборачивая самые простые случаи (коих большинство в таких проверках) в удобную, компактную конструкцию.
В третьем это не глупость, это называется функциональный подход, когда разделяют состояние и работу с ним.
Почитайте про чистые функции и вообще про принципы ФП что ли.
Хотя это далеко не самый лучший пример конечно.
на лямбдах всё вполне читабельно получается и лаконичнее.
Вот например преобразование массива строк-ролей в роли для Spring Security: gist.github.com/BorzdeG/675fac06a6338dad10a7
Вот например преобразование массива строк-ролей в роли для Spring Security: gist.github.com/BorzdeG/675fac06a6338dad10a7
Java мертва, backward compatibility, как минимум, одна из причин. Kotlin (http://kotlinlang.org) позволяет писать гораздо более лаконичный и читабельный код.
Я совсем не понял часть про DRY — вместо простого решения вы написали кучу стремного и непонятного кода и радуетесь этому. В чем профит?
Пока к Java 8 еще не притрагивался, но есть вопрос по первой части… А можно объединить «получение последней статьи» и «получить название первой Java-статьи»
Т.е. сделать как-то так:
getFirstJavaArticle().orElseGet(this::fetchLatestArticle).map(Article::getTitle);
Т.е. сделать как-то так:
getFirstJavaArticle().orElseGet(this::fetchLatestArticle).map(Article::getTitle);
orElseGet
Был код в Oracle джедаями писан.
Вот если бы в андроид это дело всё. без уретралямбд всяких.
Без чего?!
Опечатался, имел ввиду github.com/orfjackal/retrolambda
андроид «на днях» перешёл на java7, а вы про 8 говорите…
не исправляйте )
UFO just landed and posted this here
А можно поподробнее про очень узкую нишу у ФП?
А то мужики пилят понимаешь сервера, игры, графические редакторы, сайтики различные, и не в курсе, что это все очень узкая ниша…
А то мужики пилят понимаешь сервера, игры, графические редакторы, сайтики различные, и не в курсе, что это все очень узкая ниша…
UFO just landed and posted this here
>Можно придумать пример, где функциональный подход будет выигрывать. Но сложно.
Facepalm…
Достаточно почитать про ФП и правильно переписать проект на Java8, что бы понять, что вы в корне ошибаетесь во всех высказываниях.
Facepalm…
Достаточно почитать про ФП и правильно переписать проект на Java8, что бы понять, что вы в корне ошибаетесь во всех высказываниях.
>> Алгоритм обработки коллекции, записанный в функциональном виде, чаще всего проигрывает в читабельности, выразительности, расширяемости в сравнении с императивным видом.
Это все, мягко говоря, субъективно. Когда в C# появились лямбды семь лет назад, многие комментировали это слово в слово как вы. А сейчас уже очень тяжело найти код без LINQ по объектам, и всем все понятно. Это вопрос привычки.
Это все, мягко говоря, субъективно. Когда в C# появились лямбды семь лет назад, многие комментировали это слово в слово как вы. А сейчас уже очень тяжело найти код без LINQ по объектам, и всем все понятно. Это вопрос привычки.
А можно поподробнее про мужиков, которые пилят сервера, игры, графические редакторы, сайтики различные на чистом FP?
Что именно надо подробнее?
Мало что ли примеров на ФП или в гугле забанили?
Мало что ли примеров на ФП или в гугле забанили?
Ну, вы вообще о каком ФП говорите? Об использовании функций высшего порядка в императивных языках, или о проектах, которые на чистом FP (e.g., haskell) написаны? Если (2), то да, примеров мало.
Я 13 лет в Java EE (про мужиков, которые пилят сервера) и еще ни разу не встретил ни в одном проекте синтаксиса выше Java 5. Я сам хотя бы Generics активно пользую, а чужие проекты открываю, так там вообще на Java 1.4 остановились (и это не легаси, а свежие проекты, использующие Java 6-7, причем разработанные матерыми профи). Сам тоже смотрю нововведения и не вижу ничего, что сделало бы проект реально более читабельным/поддерживаемым/производительным/т.д… Да, это всё классные штуки, но мне доводилось разбирать код, в котором «классные штуки» применялись как вещь в себе, просто ради попробовать, и это была жуть… Синтаксический сахар — вещь на любителя, а если в проекте работает более одного человека, то тут уже можно начать и о вкусах спорить…
Я 6 лет в EE, сейчас пишем на 8ке.
Это просто замечательно, но возникают 2 вопроса:
1. Писать на 8-ке понятие растяжимое — то же ФП, например, насколько активно используется?
2. Как насчет "что сделало бы проект реально более читабельным/поддерживаемым/производительным/т.д" — проекты на 8-ке чем лучше проектов на 7-ке/6-ке/5-ке? Производительность, трудозатраты, ошибки: если на конкретном проекте сменить синтаксис на 5-ку, например, станет ли он от этого набором жуткого говнокода?
1. Писать на 8-ке понятие растяжимое — то же ФП, например, насколько активно используется?
2. Как насчет "что сделало бы проект реально более читабельным/поддерживаемым/производительным/т.д" — проекты на 8-ке чем лучше проектов на 7-ке/6-ке/5-ке? Производительность, трудозатраты, ошибки: если на конкретном проекте сменить синтаксис на 5-ку, например, станет ли он от этого набором жуткого говнокода?
1. Вполне активно. Стримы, опшналы и т.д.
2. Ну тут скорее производительность разработчиков, ошибок вроде из-за платформы не было практически (может 1-2, ничего заметного). У нас очень много работы с коллекциями — смотреться станет страшно. Есть некоторые места, где используется ФП — там, понятно, код тоже распухнет.
2. Ну тут скорее производительность разработчиков, ошибок вроде из-за платформы не было практически (может 1-2, ничего заметного). У нас очень много работы с коллекциями — смотреться станет страшно. Есть некоторые места, где используется ФП — там, понятно, код тоже распухнет.
> почти всегда простые конструкции читабельней ФП-конструкций
Вы так говорите, будто второе исключает первое.
Вы так говорите, будто второе исключает первое.
Заменили понятные 3 строчки кода на 3 непонятные + абзац объяснений что происходит и:
> Довольно круто, правда?
Если отбросить шутки, то у идеи «no raw loops» бывали хорошие примеры, хоть мне и кажется что это в целом какая-то потеря читаемости. На мой взгляд цикл сам по себе ничем не плох, если внутри не творится что-нибудь странное. Другой вариант когда это бы имело смысл если этот ваш Java стрим создовался бы сложным образом (на пример по каким-нибудь условиям или в разных функциях).
> Довольно круто, правда?
Если отбросить шутки, то у идеи «no raw loops» бывали хорошие примеры, хоть мне и кажется что это в целом какая-то потеря читаемости. На мой взгляд цикл сам по себе ничем не плох, если внутри не творится что-нибудь странное. Другой вариант когда это бы имело смысл если этот ваш Java стрим создовался бы сложным образом (на пример по каким-нибудь условиям или в разных функциях).
В плане читаемости хорошо выглядят for comprehensions в scala. Почти обычный foreach с виду, переписывается в map/flatMap/withFilter, которые реализованы у большого количества классов, что позволяет единообразно и наглядно производить различные итеративные вычисления.
Допустим, получения всех пар (i, j), где i не превосходит j выглядит так:
Первый вариант куда более удобен для чтения, сохраняет бонусы функциональных операций (например, lazyness, если оно реализуется в соответствующих типах) и также не оперирует изменяемым состоянием.
Допустим, получения всех пар (i, j), где i не превосходит j выглядит так:
for (i <- 1 to n; j <- 1 to n if i <= j) yield (i, j)
// или так:
(1 to n).flatMap({ i => (1 to n).filter(i <= _).map({ j => (i, j) }) })
Первый вариант куда более удобен для чтения, сохраняет бонусы функциональных операций (например, lazyness, если оно реализуется в соответствующих типах) и также не оперирует изменяемым состоянием.
Образ мышления автора — вот три строки проверяющие на nil а теперь три строки но уже без слова nil так что вам надо читать доку на функцию чтобы понять что тут делается — меня пугает. Я боюсь таких людей в проекте и боюсь что статья количество таких людей увеличит
Java 8 хороший релиз и в нем много полезного, но хочется статьи где будет показано как все это использовать не для запутывания кода, а наоборот для облегчения его понимания. Пока таких статей не видел.
Java 8 хороший релиз и в нем много полезного, но хочется статьи где будет показано как все это использовать не для запутывания кода, а наоборот для облегчения его понимания. Пока таких статей не видел.
Предлагаю посмотреть вот этот гайд: What's New in Java 8.
По-моему, отличное краткое руководство по нововведениям в Java 8, с простыми и понятными примерами, в которых видна разница (в сравнении с более старыми версиями) и практическая применимость.
По-моему, отличное краткое руководство по нововведениям в Java 8, с простыми и понятными примерами, в которых видна разница (в сравнении с более старыми версиями) и практическая применимость.
Новый уровень абстракции
Довольно круто, правда?
Вот не могу понять, в чем тут новый уровень абстракции (и, видимо, подразумевается что новый уровень абстракции-это абсолютное добро априори), и что тут круто?
Я вообще пишу на php (да простят меня боги)… и на php мы пользуемся «умными» коллекциями уже очень давно. Да, они не в ядре — это отдельные либы, и все же… Я все время слышу что php — недоязык и вообще очень плохой. А теперь я немного удивлен, что для многих в «крутой яве» такой подход явился откровением. Если я что-то не так сказал или не так понял, то поправьте меня.
Optional в Java — это по большей части пятое колесо, причем недоработанное.
Во-первых, он не предназначен для хранения данных. Optional не имлементирует Serializable, что делает невозможным использование в remote-интерфейсах, и persistence объектах. Поля объекта не рекомендуется делать Optionak. Также не рекомендуется использовать Optional в списках. Насчет аксессоров нет полной определенности, Java Beans спецификация обходит этот вопрос. До сих пор не утихают споры по поводу использования Optional. Вот хороший пост на этот счет: stackoverflow.com/questions/24547673/why-java-util-optional-is-not-serializable-how-to-serialize-the-object-with-suc
Во-вторых, основная проблема обертки, как и в Scala, что Optional[Integer] не является Integer, как например String? в Kotlin, что делает несовместимой всю семантику операций. Чтобы вставить поддержку Optional нужно перелопатить весь код ради сомнительной выгоды. В случае библиотеки, нужно выпускать радикально новую версию API.
В-третьих, Optional собственно не решает проблему null в Java. Он не гарантирует, что само возвращаемое значение Optional не будет null, равно как и что в коде с Optional возвращаемый не-Optional объект будет иметь ненулевое значение.
В-четвертых. Практически нет библиотек или API, которые использовли бы Optional. Даже сам Java RT API.
Optional — это чисто ФП фича, который пришел в Java вместе с лямбдами и прочей ФП-лабудой, и в традиционном контексте не имеет большого смысла. Есть более успешная попытка искоренить null в Java при помощи аннотаций @Nonnull/@Nullable, ее и нужно придерживаться. Что реально может решить проблему, так это value types, но это пока еще proposals.
Во-первых, он не предназначен для хранения данных. Optional не имлементирует Serializable, что делает невозможным использование в remote-интерфейсах, и persistence объектах. Поля объекта не рекомендуется делать Optionak. Также не рекомендуется использовать Optional в списках. Насчет аксессоров нет полной определенности, Java Beans спецификация обходит этот вопрос. До сих пор не утихают споры по поводу использования Optional. Вот хороший пост на этот счет: stackoverflow.com/questions/24547673/why-java-util-optional-is-not-serializable-how-to-serialize-the-object-with-suc
Во-вторых, основная проблема обертки, как и в Scala, что Optional[Integer] не является Integer, как например String? в Kotlin, что делает несовместимой всю семантику операций. Чтобы вставить поддержку Optional нужно перелопатить весь код ради сомнительной выгоды. В случае библиотеки, нужно выпускать радикально новую версию API.
В-третьих, Optional собственно не решает проблему null в Java. Он не гарантирует, что само возвращаемое значение Optional не будет null, равно как и что в коде с Optional возвращаемый не-Optional объект будет иметь ненулевое значение.
В-четвертых. Практически нет библиотек или API, которые использовли бы Optional. Даже сам Java RT API.
Optional — это чисто ФП фича, который пришел в Java вместе с лямбдами и прочей ФП-лабудой, и в традиционном контексте не имеет большого смысла. Есть более успешная попытка искоренить null в Java при помощи аннотаций @Nonnull/@Nullable, ее и нужно придерживаться. Что реально может решить проблему, так это value types, но это пока еще proposals.
основная проблема обертки, как и в Scala, что Optional[Integer] не является Integer
Это не проблема, а одно из главных достоинств с точки зрения надёжности. Такую обёртку нельзя куда-то нечаянно передать, забыв обработать ошибочную ситуацию.
SomeType?
же в этом плане не лучше null
с аннотациями.То, что можно передать обертку, забыв про ошибочную ситуацию — проблема реализации, а не самое идеи чтобы Optional[Integer] являлся Integer. Как раз в Kotlin это сделано очень грамотно. Нельзя передать String? туда, где ожидается String просто так. Однако, если предварительно проверить на null, то, внезапно, так сделать уже можно.
В итоге, одновременно имеем защиту от забывчивости + удобство использования.
fun foo(s: String)
val s: String?
//вот это не скомпилируется
foo(s)
//а вот это уже скомпилируется
if (s != null)
foo(s)
В итоге, одновременно имеем защиту от забывчивости + удобство использования.
Ну так по сути это все равно разные несовместимые типы, просто в Kotlin проверка на null меняет тип s со String? на String внутри соответствующей ветви. Т.е. претензия здесь не по адресу — виноват не Optional, виновато отсутствие удобного способа проверить и сузить тип.
Строго говоря, да, Вы правы. Мысль в том, что «я могу использовать String? везде, где ожидается String» (но только если компилятор знает что это безопасно). Т.е. снаружи я могу считать что «Optional[Integer] является Integer, но с дополнительными проверками». Если я правильно понял Throwable, то он имел ввиду именно это.
Собственно, если компилятор не гарантирует корректность приведения Optional[Integer] -> Integer, то я согласен, это получается косяк в безопасности и так делать нельзя.
Собственно, если компилятор не гарантирует корректность приведения Optional[Integer] -> Integer, то я согласен, это получается косяк в безопасности и так делать нельзя.
В Kotlin и подобных языках приведение nullables удобно закамуфлировано. Например, без лишних извратов можно передать non-null значение в функцию, принимающую nullable параметры.
Равно как и значение функции, возвращающей non-null значения, может быть присвоено nullable переменной. Плюс функция, возвращающая String? ковариантна к String, т.е. при переопределении можно String? заменить на String:
Основной плюс в том, что nullable types — это исключительно фишка компилятора, также как и generics. Не существует реального типа String?.. Напр. нельзя написать String?::class, нельзя унаследовать nullable type, etc. В итоге все транслируется в обычные ссылочные типы, которые по определению уже null. С Optional это не так — это реальный тип данных, который для каждого ссылочного значения, которое уже и так nullable, создает дополнительный объект в памяти. Так что я считаю Optional неудачной попыткой исправить nullable косяк при помощи системы типов, нежели изменениями в языке/компиляторе.
fun doSomething(param:String?);
val s : String = "aaa";
doSomething(s);
Равно как и значение функции, возвращающей non-null значения, может быть присвоено nullable переменной. Плюс функция, возвращающая String? ковариантна к String, т.е. при переопределении можно String? заменить на String:
open class Class1 {
open fun doSomething():String?;
}
open class Class2 : Class1() {
override open fun doSomething():String;
}
Основной плюс в том, что nullable types — это исключительно фишка компилятора, также как и generics. Не существует реального типа String?.. Напр. нельзя написать String?::class, нельзя унаследовать nullable type, etc. В итоге все транслируется в обычные ссылочные типы, которые по определению уже null. С Optional это не так — это реальный тип данных, который для каждого ссылочного значения, которое уже и так nullable, создает дополнительный объект в памяти. Так что я считаю Optional неудачной попыткой исправить nullable косяк при помощи системы типов, нежели изменениями в языке/компиляторе.
>> без лишних извратов можно передать non-null значение в функцию, принимающую nullable параметры.
То, что String — это подтип String?, это как раз хорошо и правильно, никакого камуфлирования. В C# аналогично int — это подтип Nullable, во всяком случае, с подстановочной т.з. (но не наоборот!)
>> Основной плюс в том, что nullable types — это исключительно фишка компилятора, также как и generics. Не существует реального типа String?.. Напр. нельзя написать String?::class, нельзя унаследовать nullable type, etc.
А вот это уже косяк. И непонятно, почему вы его приводите в качестве фичи. Понятно, что это ограничение рантайма, так же, как и type erasure у generics, но что в этом хорошего?
Ну и в любом случае это не значит, что такого типа нет. В TypeScript вот вообще все типы исключительно этапа компиляции, а в рантайме понятия «тип» нет вообще, но это же не значит, что типы там не настоящие.
>> итоге все транслируется в обычные ссылочные типы, которые по определению уже null. С Optional это не так — это реальный тип данных, который для каждого ссылочного значения, которое уже и так nullable, создает дополнительный объект в памяти.
Это уже косяк конкретной реализации Optional в Java. Хотя я могу их понять — из-за того, что внятной семантики у null не было, иногда им приходится различать «значение есть, но оно null» от «значения нет».
Но это, опять же, не проблема самой идеи с optional-типами, а данной конкретной её инкарнации. Те самые nullable-типы в Kotlin — это и есть optional, просто с более удобным сахаром и более внятной семантикой.
То, что String — это подтип String?, это как раз хорошо и правильно, никакого камуфлирования. В C# аналогично int — это подтип Nullable, во всяком случае, с подстановочной т.з. (но не наоборот!)
>> Основной плюс в том, что nullable types — это исключительно фишка компилятора, также как и generics. Не существует реального типа String?.. Напр. нельзя написать String?::class, нельзя унаследовать nullable type, etc.
А вот это уже косяк. И непонятно, почему вы его приводите в качестве фичи. Понятно, что это ограничение рантайма, так же, как и type erasure у generics, но что в этом хорошего?
Ну и в любом случае это не значит, что такого типа нет. В TypeScript вот вообще все типы исключительно этапа компиляции, а в рантайме понятия «тип» нет вообще, но это же не значит, что типы там не настоящие.
>> итоге все транслируется в обычные ссылочные типы, которые по определению уже null. С Optional это не так — это реальный тип данных, который для каждого ссылочного значения, которое уже и так nullable, создает дополнительный объект в памяти.
Это уже косяк конкретной реализации Optional в Java. Хотя я могу их понять — из-за того, что внятной семантики у null не было, иногда им приходится различать «значение есть, но оно null» от «значения нет».
Но это, опять же, не проблема самой идеи с optional-типами, а данной конкретной её инкарнации. Те самые nullable-типы в Kotlin — это и есть optional, просто с более удобным сахаром и более внятной семантикой.
UFO just landed and posted this here
Гарантирует:
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
Проблема в другом.
Вот и получается, что без Optional проверять на null все равно нужно, а с Optional вообще получаем три значения вместо двух. Так что, согласен, @Nonnull/@Nullable гораздо предпочтительнее (в контексте java).
public Optional<String> foo(); //может вернуть null, вместо empty()
public String bar(); //может вернуть null
Вот и получается, что без Optional проверять на null все равно нужно, а с Optional вообще получаем три значения вместо двух. Так что, согласен, @Nonnull/@Nullable гораздо предпочтительнее (в контексте java).
Я не спорю с утверждением, что метод, возвращающий Optional может вернуть null, хотя это должно трактоваться, как ошибка и требовать устранения.
Я спорю со второй частью утверждения Throwable:
Я спорю со второй частью утверждения Throwable:
В-третьих, Optional собственно не решает проблему null в Java. Он не гарантирует, что само возвращаемое значение Optional не будет null, равно как и что в коде с Optional возвращаемый не-Optional объект будет иметь ненулевое значение.Optional.get() всегда вернет не-null или кинет исключение.
Я плохо выразился. Имелось ввиду, что там, где возвращается Optional можно сделать вывод, что значение nullable, тогда как там, где возвращается не-Optional никакого вывода сделать нельзя.
Проблема nullable не решается Optional, потому что его целью является ссылочный тип который изначально nullable по определению. Лишь дополнительно напрягает программиста распаковкой-запаковкой значений, и проверками, в которых как правило в 90% случаев нет необходимости.
Решение проблемы заключается в обратном: указать, что значение как раз не является nullable и оно safe для операций. Если идти путем Optional, то потребуется монада Some, и всего лишь переписать весь код, заменяя все non-nullable свободные ссылочные типы T на Some. Делов-то! Либо использовать другой способ, чтобы научить компилятор отличать non-nullable значения от всех остальных, например при помощи аннотаций. Или ввести в язык value-types.
P.S. Для любителей геморроя с Optional:
// мне нравятся Optinal и теперь я буду их везде использовать
public Optional<String> getMyValue();
// а это мой старый код, который используется в куче мест и мне лень все это переписывать. Может ли он возвращать nullable, я уже и забыл.
public String getMyAnotherValue();
// а это код Васи, который поклялся, что тоже будет везде использовать Optional, но почему-то здесь иногда возвращает null.
public String getHisValue();
Проблема nullable не решается Optional, потому что его целью является ссылочный тип который изначально nullable по определению. Лишь дополнительно напрягает программиста распаковкой-запаковкой значений, и проверками, в которых как правило в 90% случаев нет необходимости.
Решение проблемы заключается в обратном: указать, что значение как раз не является nullable и оно safe для операций. Если идти путем Optional, то потребуется монада Some, и всего лишь переписать весь код, заменяя все non-nullable свободные ссылочные типы T на Some. Делов-то! Либо использовать другой способ, чтобы научить компилятор отличать non-nullable значения от всех остальных, например при помощи аннотаций. Или ввести в язык value-types.
P.S. Для любителей геморроя с Optional:
class MyBean {
// надо optional поле
private Optional<String> value;
// это сгенерит любая IDE
public Optional<String> getValue() {return value;}
public void setValue(Optional<String> value) {this.value = value;}
}
// где-то в коде
MyBean b = new MyBean();
// так можно, компилятор не матюгнется
b.setValue(null);
b.getValue().orElse(""); // NPE
// теперь сделаем все корректно:
class MyBean {
private String value; // чтобы bean был Serializable поля не должны быть Optional
// для каждого поля надо доработать аксессоры ручками
public Optional<String> getValue() {return Optional.ofNullable(value);}
public void setValue(Optional<String> value) {
if (value == null) this.value = null; // можно кинуть NPE в этом случае
this.value = value.orElse(null);
}
}
а чем плох такой Bean?
class MyBean {
private String value;
public Optional<String> getValueSafe() { return Optional.ofNullable(value); }
public String getValue() { return this.value; }
public void setValue(String value) {
this.value = value;
}
}
Избыточностью.
Во-первых, задолбает писать для каждого поля safe-геттер. IDE такое не предлагает.
Во-вторых, если наша задача — минимизировать возможность ошибки, то здесь остается такая дверь ввиде традиционного геттера. Если я сделаю такой бин, угадайте какой из двух геттеров в 90% случаев будет использовать Вася?
Во-третьих, никто так не делает. Ни одна спецификация, рекомендация, или просто use-case не содержит подобного. Все библиотеки, фреймворки, работающие со свойствами, будут использовать традиционные аксессоры.
И, наконец, есть решение лучше и проще:
Во-первых, задолбает писать для каждого поля safe-геттер. IDE такое не предлагает.
Во-вторых, если наша задача — минимизировать возможность ошибки, то здесь остается такая дверь ввиде традиционного геттера. Если я сделаю такой бин, угадайте какой из двух геттеров в 90% случаев будет использовать Вася?
Во-третьих, никто так не делает. Ни одна спецификация, рекомендация, или просто use-case не содержит подобного. Все библиотеки, фреймворки, работающие со свойствами, будут использовать традиционные аксессоры.
И, наконец, есть решение лучше и проще:
class MyBean {
@Nullable
private String value;
// а это сгенерит умная IDE
@Nullable
public String getValue() {return value;}
public void setValue(@Nullable value) {this.value = value;}
}
> Во-вторых, основная проблема обертки, как и в Scala, что Optional[Integer] не является Integer, как например String
В Scala это не проблема, куча механизмов как упростить ваш код pattern matching, implicit conversions, for comprehensive и так далее, это в Java Optional выглядит как собаке пятая нога, просто потому что когда получаешь объект этого типа пытаешься судорожно от него избавиться.
Наоборот в Scala радует что можно до последнего возиться с Option и распаковать значение уже тогда когда это действительно нужно.
Вот ваш код ниже на Scala
def foo(s: String): String =…
Option(«Hello world!»).map(foo(_)) match {
case Some(value) =>
case None =>…
}
В Scala это не проблема, куча механизмов как упростить ваш код pattern matching, implicit conversions, for comprehensive и так далее, это в Java Optional выглядит как собаке пятая нога, просто потому что когда получаешь объект этого типа пытаешься судорожно от него избавиться.
Наоборот в Scala радует что можно до последнего возиться с Option и распаковать значение уже тогда когда это действительно нужно.
Вот ваш код ниже на Scala
def foo(s: String): String =…
Option(«Hello world!»).map(foo(_)) match {
case Some(value) =>
case None =>…
}
Sign up to leave a comment.
Java 8: Овладейте новым уровнем абстракции