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

В данной статье большее внимание уделяется на четыре вещи, представленные в Java 8, которые помогут вам овладеть новым уровнем абстракции.




1. Больше никаких циклов


Я уже говорил это ранее, и скажу снова. Попрощайтесь с циклами, и приветствуйте Stream API. Дни обсуждения Java о циклах элементов подходят к концу. Со Stream API в Java мы можем сказать что мы хотим получить, вместо того чтобы говорить как этого можно добиться.

Давайте рассмотрим следующий пример.

Мы имеем список статей, каждая из которых имеет свой список тегов. Теперь мы хотим получить первую статью, содержащую тег «Java».

Взглянем на стандартный подход.
public Article getFirstJavaArticle() {
 
    for (Article article: articles) {
        if (article.getTags().contains("Java")) {
            return article;
        }
    }
 
    return null;
}


Решим задачу, используя Stream API.
public Optional<Article> getFirstJavaArticle() {  
    return articles.stream()
        .filter(article -> article.getTags().contains("Java"))
        .findFirst();
}


Довольно круто, правда?

Сначала мы используем filter, чтобы найти все статьи, которые содержат тег Java, далее с помощью findFirst получаем первое включение.

Возникает вопрос: почему мы должны фильтровать весь список, если нам необходимо только первое включение? Так как потоки… ленивы и filter возвращает поток, вычисления происходят до тех пор, пока не будет найдено первое включение.

Я уже ранее посвятил статью о замене циклов на stream API. Прочтите его, если вам нужно больше примеров.

2. Избавьтесь от null-проверок


Можно заметить, что в предыдущем примере мы можем вернуть Optional<Article>.

Optional — это контейнер объекта, который может содержать или не содержать ненулевое значение.

Этот объект имеет некоторые функции высшего порядка, избавляющие от добавления повторяющихся if null/notNull проверок, что позволяет нам сфокусироваться на том, что мы хотим сделать.

Теперь усовершенствуем метод getFirstJavaArticle. Если не найдётся Java-статья, мы будем рады получению последней статьи.

Давайте рассмотрим, как выглядит типичное решение.
    Article article = getFirstJavaArticle();

    if (article == null) {
        article = fetchLatestArticle();
    }


А теперь решение с использованием Optional<T>.
getFirstJavaArticle()  
    .orElseGet(this::fetchLatestArticle);


Отлично выглядит, не правда ли?

Ни лишних переменных, ни if-конструкций, ни каких-либо упоминаний null. Мы просто используем Optional.orElseGet, чтобы сказать, что мы хотим получить, если не будет найдено значение.

Взглянем на другой пример использования Optional. Предположим, мы хотим получить название первой Java-статьи, если она будет найдена.

Опять же, используя типичное решение, нам пришлось бы добавлять null-проверку, но… знаете что? Optional здесь, чтобы спасти этот день.
playGround.getFirstJavaArticle()  
    .map(Article::getTitle);


Как вы можете видеть, Optional реализует функцию высшего порядка map, помогая нам применить функцию к результату, если он есть.

Для большей информации об Optional смотрите документацию.

3. Создайте свои функции высшего порядка


Как видите, Java 8 поставляется с уже готовым набором функций высшего порядка, и вы можете с ними творить чудеса. Но зачем останавливаться? И почему бы не создать свои собственные функции высшего порядка?

Единственное, что нужно, чтобы сделать функцию высшего порядка, — это взять один из функциональных интерфейсов Java, или интерфейса SAM-типа в качестве аргумента и / или возвращать одно из них.

Чтобы проиллюстрировать это, рассмотрим следующий сценарий.

У нас есть принтер, который может печатать различные виды документов. Перед печатью принтер должен прогреться, а после печати перейти в спящий режим.

Теперь мы хотим получить возможность посылать команды на принтер без заботы о его процедурах включения и выключения. Это может быть решено путём создания функции высшего порядка.
public void print(Consumer<Printer> toPrint) {

    printer.prepare();
    toPrint.accept(printer);
    printer.sleep();
}


Как видите, мы используем Consumer<Printer>, являющийся одним из функциональных интерфейсов, в качестве аргумента. Затем мы выполняем данную функцию в качестве шага между процедурами запуска и отключения.

Теперь мы можем с легкостью использовать наш принтер, не заботясь ни о чём другом, кроме того, что мы хотим распечатать.
// Распечатать одну статью
printHandler.print(p -> p.print(oneArticle));

// Распечатать несколько статей
printHandler.print(p -> allArticles.forEach(p::print));


Для более подробного примера прочтите мою статью о том, как создать TransactionHandler.

4. Опасайтесь дублирования. Принцип DRY


Написание функций — дело легкое и быстрое. Тем не менее, с лёгким написанием кода приходит желание дублирования.

Рассмотрим следующий пример.
public Optional<Article> getFirstJavaArticle() {  
    return articles.stream()
        .filter(article -> article.getTags().contains("Java"))
        .findFirst();
}


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

Весьма заманчивым является создавать новые потоки. Они настолько малы и их так просто можно сделать, как же это может навредить? Напротив, небольшие участки кода должны мотивировать принцип DRY дальше.

Давайте реорганизуем наш код. Для начала сделаем нашу функцию getFirstJavaArticle более универсальной — она будет принимать предикат в качестве аргумента, чтобы отфильтровать статьи в соответствии с тем, что нам нужно.
public Optional<Article> getFirst(Predicate<Article> predicate) {
    return articles.stream()
        .filter(predicate)
        .findFirst();
}


Попробуем теперь воспользоваться этой функцией, чтобы получить несколько различных статей.
getFirst(article -> article.getTags().contains("Java"));
getFirst(article -> article.getTags().contains("Scala"));
getFirst(article -> article.getTitle().contains("Clojure"));


И, тем не менее, мы всё ещё имеем дело с повторяющимся кодом. Вы можете видеть, что используется один и тот же предикат для различных значений. Давайте попробуем удалить эти дублирования посредством интерфейса Function. Теперь наш код выглядит следующим образом.
Function<String, Predicate<Article>> basedOnTag = tag -> article -> article.getTags().contains(tag);

getFirst(basedOnTag.apply("Java"));
getFirst(basedOnTag.apply("Scala"));


Отлично! Я бы сказал, что этот код соответствует принципу DRY, не так ли?

Я надеюсь, что эта статья оказалась для вас полезной и придала несколько идей относительно того, что бы вы могли сделать на более высоком уровне абстракции с использованием Java 8 и функциональных особенностей.

Комментарии (94)


  1. DbLogs
    19.04.2015 02:36
    +3

    Сам еще не перешел на Java 8, но глядя на это все стало весьма интересно: а кто-нибудь делал замеры производительности Stream API vs. for/foreach. И есть ли какие-то общие рекомендации когда и что лучше использовать.


    1. grossws
      19.04.2015 02:40
      +3

      Совсем недавно было: habrahabr.ru/post/255813


    1. lazant
      19.04.2015 16:05
      +3

      Вот тут есть кое-какое сравнение


    1. ikirin
      20.04.2015 09:56

      Итерация через Stream API задействует все ядра многоядерной системы, for/foreach нет.


      1. grossws
        20.04.2015 12:59
        +1

        Без вызова parallel() тоже?


        1. ikirin
          21.04.2015 11:22

          Без вызова похоже нет :( Простите про parallel() я забыл.


      1. Semenych
        20.04.2015 15:06
        +1

        Stream API имеет один недостаток — там для всех streams используется один и тот же пул так что если у вас в программе много много поточной обработки то могут быть очень странные побочные эффекты


        1. senia
          20.04.2015 20:12
          +1

          При желании можно использовать кастомный пул. Если код, использующий Stream API, выполняется в кастомном пуле, то Stream API из него не выйдет.


        1. TimReset
          20.04.2015 23:50

          А пример можно? Хотя бы гипотетический? Т.е. в чём будет заключаться возможные проблемы?


          1. Semenych
            21.04.2015 00:33
            +4

            Ну пример вполне не гипотетический. Если все все программисты в мире понимают что ForkJoin пул можно использовать только для легковесных коротких операций то ОК.
            Но предположим я хочу использовать stream API (оно же так здорово заменяет циклы) и для каждого элемента списка скажем сделать запрос в БД или к какому-то третьестороннему API. В общем-то это не такой уж редкий случай.
            Логично использовать parallel stream чтобы выполнить всю связку запросов быстрее.
            Но если не знать о том что пул один на все-все приложение, то пока выполняются эти запросы все остальные parallel streams будут стоять и ждать.
            Причем это будет достаточно «весело» в одном месте приложения невинные операции будут занимать иногда 10 мс, а иногда 10,000 мс.
            Опять же если все программисты в вашем проекте и все разработчики библиотек используют parallel streams правильным образом — все хорошо, но достаточно попасться одному джуниору недочившему доки и все пойдет криво.


            1. TimReset
              21.04.2015 11:33

              Понятно. Спасибо за объяснение!


            1. gurinderu
              21.04.2015 13:49

              А в вашем решении есть какой-либо шанс сделать асинхронный API?


  1. grossws
    19.04.2015 02:39
    +9

    Да, после scala оно выглядит довольно жалко. Нет в мире совершенства.


    1. senia
      19.04.2015 13:42
      +1

      Надо отдать должное и Java 8: ParView в Scala так и нет, тут Stream API впереди.


      1. grossws
        19.04.2015 19:27

        Видимо, исходили из разных задач. В 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 будет иметь тип Supplier<Optional<Article>>, а не Supplier<Article>.


        1. lany
          22.04.2015 08:25

          куча статических импортов, без которых с использованием 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)) или ещё что-нибудь. Спецификация обработки ошибки может быть разной, и надо её явно указать.


          1. lany
            22.04.2015 09:05
            +4

            Кажется, понял, что вы имели в виду про apply. Java хранит ценное качество, что для понимания написанного требуется довольно узкий контекст. Благодаря этому тут не так много синтаксического сахара, как в других языках. Если перед круглыми скобками стоит идентификатор, это значит, что идентификатор — метод, а не десяток разных вещей в зависимости от контекста, как в некоторых других языках. Это очень хорошая фича, чтобы от неё взять и отказаться. Поэтому и нельзя написать basedOnTag("Java"). Это бы позволило писать код, где уже непонятно, что перед нами — объект или метод (в данном случае basedOnTag — объект). Ну и плюс та же проблема с пространствами имён: в текущем классе может быть метод с именем basedOnTag. Может, мы хотим его вызвать?


            1. grossws
              22.04.2015 20:44

          1. grossws
            22.04.2015 20:44

            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, где этот контекст не выводится статически (во время компиляции), но разрешается во время исполнения. Больше гибкости, больше метапрограммирования, больше шансов для выстрела в ногу.


            1. lany
              22.04.2015 21:46
              +1

              в 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, когда оно уже есть?


              1. grossws
                22.04.2015 22:36

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

                Ну вот в том-то и дело, что у Java свой путь и своя философия, и статическое разрешение — часть её. Зачем создавать ещё один smalltalk или ruby, когда оно уже есть?
                Незачем. Никто и не агитирует, вроде.


        1. lany
          27.04.2015 11:22
          +1

          Закоммитил, кстати, foldLeft. Подумаю ещё, потестирую, но наверно пусть будет. Теоретически он может даже что-то выиграть с параллельности, если, например, операция в foldLeft долгая и перед ней долгий map. Но в целом если можно использовать reduce, то стоит это делать.

          Вот с foldRight засада: ему требуется память на весь поток. В Scala то же самое. Вон люди пишут, как выстрелить себе в ногу. Вдобавок пока предыдущие операции потока не выполнены, foldRight даже начать нельзя, то есть тут параллельность убивается полностью. То есть реализация foldRight должна быть вроде
          List<T> list = toList(); IntStreamEx.range(list.size()).map(i -> list.get(list.size()-1-i)).foldLeft(...)
          Причём второй поток должен выполняться в том же пуле, что и первый. Пока добавлять не буду.

          На примитивных потоках foldLeft добавить можно, но там другая проблема: нет стандартного функционального интерфейса для функции, принимающей примитив и объект и возвращающей объект. Можно добавить такой интерфейс, но пока не хочется. Если кому-то сильно приспичит, пусть boxed() вызывают.


          1. senia
            27.04.2015 19:40
            +1

            В Scala с foldRight проблема не на всех коллекциях. На индексируемой ленивой дополнительна память не требуется. Ну а с параллельностью и foldLeft не дружит.


            1. lany
              27.04.2015 19:57
              +1

              Ну дело в том, что в Java потоки — это вообще не коллекции. А с параллельностью 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, конечно.


              1. senia
                27.04.2015 20:29

                В этом отношении foldLeft от foldRight не должен отличаться в общем случае. Они вообще близнецы и разница только в конкретных реализациях. Стримы — не коллекции, несомненно, но они обладают информацией об исходной коллекции и для индексируемых коллеций left и right — условности.


                1. lany
                  28.04.2015 03:55

                  Ну вот в сорцах-то видно, что ни разу они не близнецы даже для линейных последовательностей с быстрым случайным доступом. Реализация foldLeft — обычный императивный цикл, а foldRight — рекурсия, причём не хвостовая. По ссылке, что я дал выше, как раз сравнивается

                  list.foldRight("X")((a,b) => a + b)
                  list.reverse.foldLeft("X")((b,a) => a + b)

                  И получается, что они эквивалентны, но при этом второй вариант стек не жрёт.

                  С точки зрения стримов индексируемая коллекция — исключительно частный случай. Коллекция может быть конкатенацией нескольких коллекций, может быть результатом flatMap, может прийти после filter+limit, может вообще прийти из файла или из сокета. Left и right — далеко не условности. Left гораздо проще, чем right. У стримов, например, даже операции reverse нету.


                  1. senia
                    28.04.2015 08:43
                    +1

                    Смотря в каких сорцах — я же несколько раз написал, что зависит от импелментации.

                    Далее: мы говорили про параллельность, а это значит, что кроме прочего исходная коллекция должна быть разбиваемой на части, иначе накладные расходы могут сожрать все преимущества. В той же Scala collection.par для индексируемой коллекции создает обертку, а для не индексируемой — копирует в индексируемую.

                    Раз мы говорили про параллельные вычисления, то folrLeft может начать работу после завершения обработки первого чанка, foldRight — после последнего. Причем «первый» и «последний» здесь — не про порядок выполнения, ничто не мешает последнему выполниться раньше и даже, при определенных усилиях при реализации, первым начать выполняться.

                    Кстати: 10 элементов и слип — это крайне странный способ тестирования параллельных вычислоений.


                    1. lany
                      28.04.2015 08:54

                      Ну так вы можете написать свой бенчмарк, который лучше и правильнее :-)


    1. kerkomen
      19.04.2015 16:39

      Да уж. И странно, что Haskell ещё не упомянули в комментариях…


  1. Hixon10
    19.04.2015 02:43
    +4

    Хм, увидел название статьи, думал, будет что-то новое, оригинальное. Прочитал статью, не увидел ничего нового. Потом подумал: «ага, это же перевод; тогда, наверное, оригинал написан весной 2014: именно тогда был бум простых примеров по Java 8». Ан нет, статья свежак — 13 апреля 2015 года.

    В любом случае, спасибо за проделанную работу!


  1. dkukushkin
    19.04.2015 03:50
    +36

    Довольно круто, правда?

    Как C#-щик хочу поздравить всех Java-истов с разморозкой!


    1. GrigoryPerepechko
      19.04.2015 11:08
      +1

      А какая разница на чем вы пишете? Поздравлять вроде можно и без указания собственной принадлежности.


      1. T-D-K
        19.04.2015 11:20
        +6

        Он тонко намекал на это: en.wikipedia.org/wiki/Language_Integrated_Query


        1. gurinderu
          19.04.2015 13:11
          -6

          А я толсто намекну на www.querydsl.com и www.jooq.org


          1. onikiychuka
            19.04.2015 15:10
            +7

            Вы сравниваете совсем разные вещи.
            Как минимум используя Linq можно писать запросы и к удаленному хранилищу и к любой локальной структуре данных в едином виде. Это очень полезно.
            Jooq и querydsl — это возможность строить запросы к базе.
            Тк я работал только с Jooq, то могу сказать только за него — синтаксис менее понятный чем у Linq.
            Ну и с linq вся фишка в поддержке рантайма, а именно в том что вы можете получить доступ к AST лямбд которые вы передаете в Linq методы. Это позволяет писать куда более простой и читабельный код.


        1. GrigoryPerepechko
          19.04.2015 14:53
          +3

          Эээм, то есть теперь каждый человек который пишет на языке где поддерживается project/filter/fold может прийти и ехидно «поздравить с разморозкой»? Это детский говнизм от которого коробит, а никакой не намёк.


          1. Danov
            19.04.2015 15:59
            -7

            На 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. Может где и недосмотрел противоречия.


            1. Yuuri
              20.04.2015 17:06
              +2

              C# медленно развивается?..


    1. stack_trace
      19.04.2015 12:35

      del


  1. asm0dey
    19.04.2015 10:06
    +4

    Мне кажется, что вот это уже лишнее: basedOnTag.apply(«Java»).
    Тут бы стоило сделать обычный метод, который возвращает предикат по тэгу. А то вот это вот apply выглядит достаточно уродлив. А было бы просто predicateByTag(«Java»).


  1. Cobolorum
    19.04.2015 10:15

    Примеры притянутые так за уши так что уши оторвались от головы.
    В первом случаи читабильность упала просто на порядок. Или сделайте проверку на «Java» AND/OR «Basic». Я считаю что ваш пример зарефакторится сразу в классический вариант.
    Во втором проверка на null это способ защиты от сбоев/ошибок в программе. А как известно 50 процентов работы любой программы это обработка ошибок. В вашем варианте все придется обрабатывать в исключениях и блок исключений у вас лопнет от объема кода.
    В 3-ем примере с принтером вообще глупость. Принтер это объект и объект сам должен знать и уметь обрабатывать свое состояние.


    1. stepik777
      19.04.2015 11:06
      +5

      А как известно 50 процентов работы любой программы это обработка ошибок. В вашем варианте все придется обрабатывать в исключениях и блок исключений у вас лопнет от объема кода.
      Чего? Вы статью читали? Там обработка ошибок прямо на месте, без исключений.


    1. solver
      19.04.2015 11:14
      +5

      Ну вообще-то это движуха в сторону FP.
      Так что ваш ответ тоже за уши притянут.
      В первом случае читабельность упала только у тех кто, извините, «в танке».
      Ведь это самый простой случай. Как только добавится пару условий и пару функций обработчиков, классический Java код превратится в жуткий, километровый бойлерплейт.
      Во втором случае от null никуда не уходят, проверки не убираются. Optional просто позволяет удобно работать с ними, избавляя от километров тупого java бойлерплейта, оборачивая самые простые случаи (коих большинство в таких проверках) в удобную, компактную конструкцию.
      В третьем это не глупость, это называется функциональный подход, когда разделяют состояние и работу с ним.
      Почитайте про чистые функции и вообще про принципы ФП что ли.
      Хотя это далеко не самый лучший пример конечно.


    1. Borz
      19.04.2015 12:11
      -2

      на лямбдах всё вполне читабельно получается и лаконичнее.
      Вот например преобразование массива строк-ролей в роли для Spring Security: gist.github.com/BorzdeG/675fac06a6338dad10a7


  1. Develar
    19.04.2015 10:39
    -18

    Java мертва, backward compatibility, как минимум, одна из причин. Kotlin (http://kotlinlang.org) позволяет писать гораздо более лаконичный и читабельный код.


  1. defuz
    19.04.2015 11:15
    +8

    Я совсем не понял часть про DRY — вместо простого решения вы написали кучу стремного и непонятного кода и радуетесь этому. В чем профит?


    1. kalterfive Автор
      19.04.2015 13:19

      Так там объясняются причины.

      А можно подробнее? Стрёмный и непонятный код — понятие растяжимое.


  1. agent10
    19.04.2015 11:47

    Пока к Java 8 еще не притрагивался, но есть вопрос по первой части… А можно объединить «получение последней статьи» и «получить название первой Java-статьи»
    Т.е. сделать как-то так:
    getFirstJavaArticle().orElseGet(this::fetchLatestArticle).map(Article::getTitle);


    1. Rasifiel
      19.04.2015 12:06

      После orElseGet у вас будет объект класса Article, поэтому:
      getFirstJavaArticle().orElseGet(this::fetchLatestArticle).getTitle()


  1. fshp
    19.04.2015 13:00
    +5

    orElseGet

    Был код в Oracle джедаями писан.


  1. Nagg
    19.04.2015 13:46
    +5

    Вот если бы в андроид это дело всё. без уретралямбд всяких.


    1. Yuuri
      19.04.2015 14:38
      +7

      Без чего?!


      1. Nagg
        19.04.2015 14:39
        +4

        Опечатался, имел ввиду github.com/orfjackal/retrolambda


    1. Terranz
      20.04.2015 09:56
      +2

      андроид «на днях» перешёл на java7, а вы про 8 говорите…


    1. r00tGER
      20.04.2015 11:58
      +2

      не исправляйте )


  1. vsb
    19.04.2015 15:34
    +6

    Примеры не очень показательные, конечно.

    Моё имхо — почти всегда простые конструкции читабельней ФП-конструкций. А если не читабельней, то и в ФП будет нечитабельная лапша и тут уже надо просто делать extract method для разделения логики на составные части с хорошими названиями. Я не против ФП, но у него очень узкая ниша. А его сейчас с радостными криками «Гип-гип-ура!» будут сувать во все дыры, как будто это что-то новое. Нет тут ничего нового, в лиспе это всё было 50 лет назад, а в хаскеле 20 лет назад и все интересующиеся уже это изучили и забыли.

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

    А уж Article::getName насколько уродливый синтаксис! Ну почему _.getName() не сделали-то? Вот этого не понимаю.


    1. solver
      19.04.2015 16:59
      +4

      А можно поподробнее про очень узкую нишу у ФП?
      А то мужики пилят понимаешь сервера, игры, графические редакторы, сайтики различные, и не в курсе, что это все очень узкая ниша…


      1. vsb
        19.04.2015 17:03
        +2

        Алгоритм обработки коллекции, записанный в функциональном виде, чаще всего проигрывает в читабельности, выразительности, расширяемости в сравнении с императивным видом.

        Это я конкретно про Java сейчас говорю. Например в Scala есть for comprehensions, которые по сути те же map/filter/etc но выглядят гораздо красивее. В Python вроде тоже что-то похожее. В Haskell тоже. Это совсем другое дело. Это ФП с человеческим лицом. Но в Java такого нет и без этого обычные for и if лучше.

        Можно придумать пример, где функциональный подход будет выигрывать. Но сложно.


        1. solver
          19.04.2015 17:12
          -1

          >Можно придумать пример, где функциональный подход будет выигрывать. Но сложно.

          Facepalm…

          Достаточно почитать про ФП и правильно переписать проект на Java8, что бы понять, что вы в корне ошибаетесь во всех высказываниях.


        1. int19h
          21.04.2015 01:34

          >> Алгоритм обработки коллекции, записанный в функциональном виде, чаще всего проигрывает в читабельности, выразительности, расширяемости в сравнении с императивным видом.

          Это все, мягко говоря, субъективно. Когда в C# появились лямбды семь лет назад, многие комментировали это слово в слово как вы. А сейчас уже очень тяжело найти код без LINQ по объектам, и всем все понятно. Это вопрос привычки.


          1. vsb
            21.04.2015 07:11

            LINQ и набор функций это две большие разницы. Понятно, что под капотом оно всё похоже, но LINQ, For Comprehensions и тд это ФП в человеческом виде. В Java же просто дали набор функциональных примитивов.


            1. int19h
              21.04.2015 08:21
              +1

              На практике, 90% кода на шарпе, с которым я имею дело, не использует сахар. Т.е. в данном случае имелся в виду именно набор функциональных примитивов.


      1. Hixon10
        19.04.2015 17:14
        +6

        А можно поподробнее про мужиков, которые пилят сервера, игры, графические редакторы, сайтики различные на чистом FP?


        1. solver
          19.04.2015 19:18
          -5

          Что именно надо подробнее?
          Мало что ли примеров на ФП или в гугле забанили?


          1. Hixon10
            19.04.2015 19:20
            +2

            Ну, вы вообще о каком ФП говорите? Об использовании функций высшего порядка в императивных языках, или о проектах, которые на чистом FP (e.g., haskell) написаны? Если (2), то да, примеров мало.


            1. solver
              19.04.2015 19:39
              -3

              Примеров мало по одной простой причине. Мейнстримом рулит ООП.

              Но всем уже понятно, что в условиях когда производительность стала измеряться не гигагерцами, а ядрами, функциональный подход не просто удобнее, он надежнее.
              Поэтому то и вспомнили о ФП. И ФП языки получили второе дыхание.


        1. bobzer
          22.04.2015 07:11

          Я 13 лет в Java EE (про мужиков, которые пилят сервера) и еще ни разу не встретил ни в одном проекте синтаксиса выше Java 5. Я сам хотя бы Generics активно пользую, а чужие проекты открываю, так там вообще на Java 1.4 остановились (и это не легаси, а свежие проекты, использующие Java 6-7, причем разработанные матерыми профи). Сам тоже смотрю нововведения и не вижу ничего, что сделало бы проект реально более читабельным/поддерживаемым/производительным/т.д… Да, это всё классные штуки, но мне доводилось разбирать код, в котором «классные штуки» применялись как вещь в себе, просто ради попробовать, и это была жуть… Синтаксический сахар — вещь на любителя, а если в проекте работает более одного человека, то тут уже можно начать и о вкусах спорить…


          1. asm0dey
            22.04.2015 07:22

            Я 6 лет в EE, сейчас пишем на 8ке.


            1. bobzer
              22.04.2015 07:30

              Это просто замечательно, но возникают 2 вопроса:
              1. Писать на 8-ке понятие растяжимое — то же ФП, например, насколько активно используется?
              2. Как насчет "что сделало бы проект реально более читабельным/поддерживаемым/производительным/т.д" — проекты на 8-ке чем лучше проектов на 7-ке/6-ке/5-ке? Производительность, трудозатраты, ошибки: если на конкретном проекте сменить синтаксис на 5-ку, например, станет ли он от этого набором жуткого говнокода?


              1. asm0dey
                22.04.2015 12:14

                1. Вполне активно. Стримы, опшналы и т.д.
                2. Ну тут скорее производительность разработчиков, ошибок вроде из-за платформы не было практически (может 1-2, ничего заметного). У нас очень много работы с коллекциями — смотреться станет страшно. Есть некоторые места, где используется ФП — там, понятно, код тоже распухнет.


    1. Yuuri
      20.04.2015 17:09
      +3

      > почти всегда простые конструкции читабельней ФП-конструкций
      Вы так говорите, будто второе исключает первое.


  1. orcy
    19.04.2015 16:16
    +6

    Заменили понятные 3 строчки кода на 3 непонятные + абзац объяснений что происходит и:
    > Довольно круто, правда?

    Если отбросить шутки, то у идеи «no raw loops» бывали хорошие примеры, хоть мне и кажется что это в целом какая-то потеря читаемости. На мой взгляд цикл сам по себе ничем не плох, если внутри не творится что-нибудь странное. Другой вариант когда это бы имело смысл если этот ваш Java стрим создовался бы сложным образом (на пример по каким-нибудь условиям или в разных функциях).


    1. grossws
      19.04.2015 22:04
      +2

      В плане читаемости хорошо выглядят for comprehensions в scala. Почти обычный foreach с виду, переписывается в map/flatMap/withFilter, которые реализованы у большого количества классов, что позволяет единообразно и наглядно производить различные итеративные вычисления.

      Допустим, получения всех пар (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, если оно реализуется в соответствующих типах) и также не оперирует изменяемым состоянием.


  1. Semenych
    19.04.2015 17:53
    +9

    Образ мышления автора — вот три строки проверяющие на nil а теперь три строки но уже без слова nil так что вам надо читать доку на функцию чтобы понять что тут делается — меня пугает. Я боюсь таких людей в проекте и боюсь что статья количество таких людей увеличит

    Java 8 хороший релиз и в нем много полезного, но хочется статьи где будет показано как все это использовать не для запутывания кода, а наоборот для облегчения его понимания. Пока таких статей не видел.


    1. yahorfilipcyk
      20.04.2015 10:42
      +2

      Предлагаю посмотреть вот этот гайд: What's New in Java 8.
      По-моему, отличное краткое руководство по нововведениям в Java 8, с простыми и понятными примерами, в которых видна разница (в сравнении с более старыми версиями) и практическая применимость.


      1. Semenych
        21.04.2015 00:39

        Спасибо толковый гайд


  1. ttools
    19.04.2015 22:30

    Новый уровень абстракции

    Довольно круто, правда?

    Вот не могу понять, в чем тут новый уровень абстракции (и, видимо, подразумевается что новый уровень абстракции-это абсолютное добро априори), и что тут круто?


  1. greabock
    20.04.2015 01:32
    +1

    Я вообще пишу на php (да простят меня боги)… и на php мы пользуемся «умными» коллекциями уже очень давно. Да, они не в ядре — это отдельные либы, и все же… Я все время слышу что php — недоязык и вообще очень плохой. А теперь я немного удивлен, что для многих в «крутой яве» такой подход явился откровением. Если я что-то не так сказал или не так понял, то поправьте меня.


    1. Hixon10
      20.04.2015 15:56

      В Крутой Джаве всегда была Крутая Гуава, в которой всё это давно уже есть (конечно же, по модулю того, какой синтаксис возможен с более старыми версиями JDK).


  1. Throwable
    20.04.2015 12:06

    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.


    1. Yuuri
      20.04.2015 17:16
      +1

      основная проблема обертки, как и в Scala, что Optional[Integer] не является Integer

      Это не проблема, а одно из главных достоинств с точки зрения надёжности. Такую обёртку нельзя куда-то нечаянно передать, забыв обработать ошибочную ситуацию. SomeType? же в этом плане не лучше миллиарднодолларовой ошибки обычного null с аннотациями.


      1. dougrinch
        20.04.2015 22:13
        +1

        То, что можно передать обертку, забыв про ошибочную ситуацию — проблема реализации, а не самое идеи чтобы Optional[Integer] являлся Integer. Как раз в Kotlin это сделано очень грамотно. Нельзя передать String? туда, где ожидается String просто так. Однако, если предварительно проверить на null, то, внезапно, так сделать уже можно.

        fun foo(s: String)
        val s: String?
        
        //вот это не скомпилируется
        foo(s)
        
        //а вот это уже скомпилируется
        if (s != null)
            foo(s)
        


        В итоге, одновременно имеем защиту от забывчивости + удобство использования.


        1. int19h
          21.04.2015 01:36

          Ну так по сути это все равно разные несовместимые типы, просто в Kotlin проверка на null меняет тип s со String? на String внутри соответствующей ветви. Т.е. претензия здесь не по адресу — виноват не Optional, виновато отсутствие удобного способа проверить и сузить тип.


          1. dougrinch
            21.04.2015 02:45

            Строго говоря, да, Вы правы. Мысль в том, что «я могу использовать String? везде, где ожидается String» (но только если компилятор знает что это безопасно). Т.е. снаружи я могу считать что «Optional[Integer] является Integer, но с дополнительными проверками». Если я правильно понял Throwable, то он имел ввиду именно это.

            Собственно, если компилятор не гарантирует корректность приведения Optional[Integer] -> Integer, то я согласен, это получается косяк в безопасности и так делать нельзя.


          1. Throwable
            22.04.2015 12:03
            +1

            В Kotlin и подобных языках приведение nullables удобно закамуфлировано. Например, без лишних извратов можно передать non-null значение в функцию, принимающую 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 косяк при помощи системы типов, нежели изменениями в языке/компиляторе.


            1. int19h
              22.04.2015 19:29
              +1

              >> без лишних извратов можно передать 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, просто с более удобным сахаром и более внятной семантикой.


    1. vsb
      21.04.2015 10:44

      > В-третьих, Optional собственно не решает проблему null в Java. Он не гарантирует, что… в коде с Optional возвращаемый не-Optional объект будет иметь ненулевое значение.

      А можете этот момент показать кодом, не совсем понятно. Вроде гарантирует.


      1. grossws
        21.04.2015 16:09

        Гарантирует:

        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);
        }


        1. dougrinch
          21.04.2015 16:53
          +1

          Проблема в другом.

          public Optional<String> foo(); //может вернуть null, вместо empty()
          
          public String bar(); //может вернуть null
          


          Вот и получается, что без Optional проверять на null все равно нужно, а с Optional вообще получаем три значения вместо двух. Так что, согласен, @Nonnull/@Nullable гораздо предпочтительнее (в контексте java).


          1. grossws
            21.04.2015 17:00

            Я не спорю с утверждением, что метод, возвращающий Optional может вернуть null, хотя это должно трактоваться, как ошибка и требовать устранения.

            Я спорю со второй частью утверждения Throwable:

            В-третьих, Optional собственно не решает проблему null в Java. Он не гарантирует, что само возвращаемое значение Optional не будет null, равно как и что в коде с Optional возвращаемый не-Optional объект будет иметь ненулевое значение.
            Optional.get() всегда вернет не-null или кинет исключение.


            1. Throwable
              22.04.2015 12:58

              Я плохо выразился. Имелось ввиду, что там, где возвращается Optional можно сделать вывод, что значение nullable, тогда как там, где возвращается не-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);
              }
              }
              


              1. Borz
                22.04.2015 14:43

                а чем плох такой 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;
                  }
                }
                


                1. Throwable
                  23.04.2015 11:34
                  +2

                  Избыточностью.
                  Во-первых, задолбает писать для каждого поля 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;}
                  }
                  


    1. vayho
      28.04.2015 09:54

      > Во-вторых, основная проблема обертки, как и в 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 =>…
      }