JSR 335 или lambda-выражения в JAVA 8

    Введение


    Посетил вчера семинар по lambda-выражениям в JAVA 8. Рассказали много интересного.

    Из интересностей:

    lambda-выражения


    Comparator<Integer> cmp = (x, y) -> (x < y) ? -1 : (x > y) ? 1 : 0;
    

    Слева обязательный интерфейс, определяющий lambda. Справа выражение. Левая часть от "->" это сигнатура, правая — реализация.

    Эта конструкция заменяет громоздкий код:

    Comparator<Integer> comparator = new Comparator<Integer> () {
      public int compare(Integer x, Integer y) {
        return (x < y) ? -1 : (x > y) ? 1 : 0;
      }
    };
    

    lambda-выражения могут имплементировать любой функциональный интерфейс.Функциональный интерфейс — это интерфейс с одним абстрактным методом (об этом ниже). Также добавится куча полезных интерфейсов вроде Factory.make, Mapper.map. Ну и еще множество гибких возможностей использования и применения.

    Также есть возможность вместо ручного описания лямбды брать ее реализацию из других классов:
    Comparator<Integer> comparator = LibraryComparator::compare; // Некая реализация из библиотеки
    


    Расширение интерфейсов default-методами (defender)


    Да, теперь методы интерфейса делятся на абстрактные (не имеют реализации) и не абстрактные (default), которые имеют некую дефолтную реализацию. Это нововведение признано упростить расширение интерфейсов базовых сущностей JAVA, да и вообще любых интерфейсов с поддержкой совместимости. Например, имеется старый интерфейс:
    public interface OldInterface {
        void method();
      }
    

    Нам его надо расширить, но чтобы старый код продолжил работать. Добавим default-метод:
    public interface OldInterface {
        void method();
        void newMethod() default {
          // default implementation
        }
      }
    

    Писать или не писать слово default в интерфейсах — обсуждается.

    Stream (bulk) операции


    Классная штука, позволяющая гораздо гибче работать с коллекциями. Например, абстрактный пример в вакууме:
    list.stream().parallel().reduce(Math::max).into(newList);
    

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

    Подробнее о новых фишках: http://openjdk.java.net/projects/lambda/

    О новых встречах: http://jug.ru/
    Записывали видео, буду рад, если кто приведет ссылку на него для общественности.

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 24

      +1
      Все замечательно — кроме дефолтного метода в интерфейсе. Это не нужно.
        +2
        Кроме того, что это позволит добавлять методы в интерфейс, не ломая бинарной совместимости, это ещё и позволит определять богатые интерфейсы, не вынуждая реализации к определению всех методов.

        Например, новому классу коллекции достаточно будет определить метод foreach, и все остальные полезные методы (map, filter, fold, ...) он получит автоматически, имея все же возможность их переопределить, если это повысит эффективность.

        Похожим образом на traits построены коллекции в Scala — очень удобно.
          0
          Во-первых, у меня есть смутные подозрения, что при добавлении метода в конец деклараций бинарная совместимость не сломается.

          Во-вторых, если нужно что-то менять по серьезному — лучше завести новый интерфейс наследующий (или не наследующий) текущий.

          В третьих, на крайний ленивый случай все таки есть Maven и его версионность артефактов.

          Эти богатые интерфейсы такие богатые :) В общем, я против — так и запишите.
            +1
            1. Пусть предположение о том, что совместимость не ломается, верно. Что вернёт только что добавленный метод интерфейса «int getInt()», будучи вызванным на классе, реализующем этот интерфейс, но скомпилированном со старой его версии?
            2. Методы, использующие этот интерфейс, тоже придётся дублировать
            3. Библиотеки, использующие разные версии интерфейсов, не смогут взаимодействовать друг с другом, т.е. все равно все перекомпилировать
            4. Записали, распишитесь вот здесь ________
              –1
              1. Если я правильно вас понял, то проблема существует и в варианте с новым дефолтным методом. Если вы вызываете какой-то новый метод, то при подключении старой версии артефакта, где такого метода нет — получим исключение, при этом не важно как был определен метод в новой версии — с дефолтным поведением или нет.

              2. Я вот не совсем понял замечание. Отмечу только, что принцип определение новых интерфейсов которые расширяют текущие интерфейсы широко определен и используется еще со времен Misrosoft COM/ActiveX. Это действительно позволяет сохранять совместимость без всякого сахара и дополнительных ухищрений.

              3. На самом деле смогут во многих случаях. На самом деле проблема-то еще шире. Ну хорошо, вот есть у вас интерфейсы с дефолтным поведением и вы надеетесь вместо старого артефакта подсунуть новый с «расширенным интерфейсом» про который новый код знает и переопределяет, а старый — нет. Но совместимость чаще всего только этим не ограничивается — обычно есть еще миллион обыкновенных классов-структур в которых в разных версиях артефактов присутствует разный набор полей и их типов, разная внутренняя логика и прочее.

              В общем я не вижу большого выигрыша тут и просто тупо бы определил:

              public interface OldInterface2 extends OldInterface { void newMethod(); }

              и заимплементировал его в новом коде, а где нужно проверял через instanceof

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

                  2-3. Пусть есть наш interface Intf, внешний класс class Cls implements Intf и наш метод void smth(Intf i). Мы хотим новую функциональность, создаем interface Intf2 extends Intf, теперь нам нужно или добавить новый метод void smth(Intf2 i), оставив старый, или изменить метод на void smth(Intf2 i) и создать адаптер
                  class Intf1ToIntf2Adapter implements Intf2 {
                    private Intf1 intf1;
                    public Intf1ToIntf2Adapter(Intf1 intf1) { this.intf1 = intf1; }
                    public void oldMethod() { intf1.oldMethod(); }
                    public void newMethod() { /* some default impl */ }
                  }
                  

                  Так вот default method — практически автоматизация второго подхода.
                    0
                    теперь нам нужно или добавить новый метод void smth(Intf2 i)


                    Ну — все же работает. Можно вообще забить и оставить void smth(Intf i) и внутри делать instanceOf. Хотя вариант с новым методом лучше.

                    или изменить метод на void smth(Intf2 i) и создать адаптер


                    То же вариант, хотя вызывающая сторона должна знать об адаптере, соответственно и об Intf2, поэтому могла бы сразу его и реализовать.

                    В общем, степень необходимость такой автоматизации для меня не так очевидна. Объяснение от SVlad, которое подтверждается после чтения PDF-ки более-менее разумно в моем понимании. Разработчики базовых библиотек JVM с одной стороны не хотят вводить новые интерфейсы (непонятно почему), а хотят сразу подредактировать базовые Collection, Set, List — при этом чтобы у людей все продолжало работать.
          +2
          эээ… возвращение к множественному наследованию? о_О
            0
            хотя если дефаулты тока в лямбдах будут пахать… то все же некрасиво
              0
              Поясните, пожалуйста, свою мысль. Я её совсем не понял.
              +1
              Множественное наследование так страшно из-за данных в предках (порядок инициализации полей, множественное непрямое наследование от одного базового класса). Поля в интерфейсы добавлять по-прежнему нельзя, так что про множественное наследование речи не идет.

                +1
                А как тогда быть, если у нас interface Int1 имеет default-метод someMethod и interface Int2 имеет также default-метод someMethod, а interface Int3 extends Int1, Int2? Какую из реализаций default-метода поставит Int3?
                  0
                  Ту, которую программист напишет в Int3, поскольку иначе программа компилироваться не будет.
                    0
                    а если class A implements Int1, Int2 реализация someMethod уже будет (а будет ли?), но откуда? :)
                      +1
                      Если A не реализует этот метод, то возникнет исключение, как только кто-нибудь попытается его вызвать (в том числе и через приведение к Int1 или Int2).
                      0
                      Т.е. внезапным добавлением метода в новую версию базового интерфейса можно сделать некоторые из его потомков некомпилябельными?
                +1
                Разве .filter(Math::max) будет работать? Как я понял у аргумента filter ожидается функция-предикат (от класса Predicate), т.е. принимающая 1 аргумент некого типа T и возвращающая boolean, т.е. max не подходит?
                  0
                  «Абстрактный пример в вакууме», потому что негде было проверить, а опыта использования еще не появилось :) На самом деле там такое выражение:
                  list.stream().reduce(Math::max).get();
                  
                • UFO just landed and posted this here
                    0
                    Спасибо!
                    0
                    Вот здесь (http://www.parleys.com/#st=5&id=3072&sl=0) можно найти прекрасное выступление с JFocus 2012 с толковыми объяснениями почему было решено сделать так или иначе, как оно работает и почему. На английском, но слушается легко и понятно.
                      0
                      Спасибо большое за рассказ, очень наслышан о новой Java 8, было интересно почитать так подробно про такую новую фичу, как лямда-выражения!

                      В дополнение к вашему репорту, про новую Java 8 и её фишки можно почитать в статье Java 8 vs. Scala: сравнение возможностей (здесь рассматривают не только лябды, но и также и другие новинки Java 8). Спасибо за пост!
                        0
                        О, нет! Только не ::

                        Only users with full accounts can post comments. Log in, please.