Статьи

Обзор новых функций Java SE 8: функциональное программирование с использованием лямбда-выражения

Эта статья из серии « Тур по новым возможностям Java SE 8 » глубоко погрузится в понимание лямбда-выражений . Я покажу вам несколько разных способов использования лямбда-выражений. Все они имеют общую реализацию функциональных интерфейсов. Я объясню, как компилятор выводит информацию из кода, такую ​​как конкретные типы переменных и что на самом деле происходит в фоновом режиме.

В предыдущей статье « Обзор новых функций Java SE 8: большие перемены в мире разработки Java », где я рассказал о том, что мы собираемся исследовать в этой серии. Я начал с ознакомления с основными функциями Java SE 8 , после чего последовал процесс установки JDK8 на платформах Microsoft Windows и Apple Mac OS X с важными советами и примечаниями, о которых следует позаботиться.

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

Исходный код размещен на моей учетной записи Github: клон от ЗДЕСЬ .

Что такое лямбда-выражение?

Возможно, самая известная новая функция Java SE 8 называется Project Lambda — попытка привнести Java в мир функционального программирования .

В терминологии информатики;


Лямбда — это анонимная функция. То есть функция без имени.

На Java;


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

Лямбда-выражение в Java SE 8 позволяет вам определять класс и отдельный метод с очень лаконичным синтаксисом, реализуя интерфейс, который имеет один абстрактный метод.

Давайте разберемся с идеей.

Лямбда-выражения позволяют разработчикам упростить и сократить свой код. Делая это более читабельным и ремонтопригодным. Это приводит к удалению более подробных объявлений классов .

Давайте посмотрим на несколько фрагментов кода.

  1. Реализация интерфейса: до Java SE 8, если вы хотите создать поток, вы сначала должны определить класс, который реализует работающий интерфейс. Это интерфейс, который имеет единственный абстрактный метод с именем Run, который не принимает аргументов. Вы можете определить класс в своем собственном кодовом файле. Файл с именем MyRunnable.java . И вы могли бы назвать класс MyRunnable, как я сделал здесь. И тогда вы реализуете один абстрактный метод.
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    public class MyRunnable implements Runnable {
      
        @Override
        public void run() {
            System.out.println("I am running");
        }
      
        public static void main(String[] args) {
            MyRunnable r1 = new MyRunnable();
            new Thread(r1).start();
        }
    }

    В этом примере моя реализация выводит литеральную строку в консоль. Затем вы возьмете этот объект и передадите его экземпляру класса потока. Я создаю экземпляр моего запускаемого объекта как объекта с именем r1. Передача его конструктору потока и вызов метода start потока. Мой код теперь будет выполняться в своем собственном потоке и в собственном пространстве памяти.

  2. Реализация внутреннего класса: Вы можете немного улучшить этот код, вместо того, чтобы объявлять свой класс в отдельном файле, вы можете объявить его как одноразовый класс, известный как внутренний класс , локальный для метода, в котором он используется.
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    public static void main(String[] args) {
      
           Runnable r1 = new Runnable() {
               @Override
               public void run() {
                   System.out.println("I am running");
               }
           };
           new Thread(r1).start();
       }

    Итак, теперь я снова создаю объект с именем r1, но я вызываю метод конструктора интерфейса напрямую. И еще раз, реализуя это единственный абстрактный метод. Затем я передаю объект в конструктор потока.

  3. Реализация анонимного класса. И вы можете сделать его еще более сжатым, объявив класс как анонимный класс , названный так, потому что ему никогда не дают имя. Я создаю экземпляр запускаемого интерфейса и сразу передаю его в конструктор потоков. Я все еще реализую метод run и все еще вызываю метод start потока.
    1
    2
    3
    4
    5
    6
    7
    8
    public static void main(String[] args) {
           new Thread(new Runnable() {
               @Override
               public void run() {
                   System.out.println("I am running");
               }
           }).start();
       }
  4. Использование лямбда-выражения: в Java SE 8 вы можете повторно кодировать этот код, чтобы значительно уменьшить его и сделать его более читабельным. Лямбда-версия может выглядеть следующим образом.
    1
    2
    3
    4
    public static void main(String[] args) {
           Runnable r1 = () -> System.out.println("I am running");
           new Thread(r1).start();
       }

    Я объявляю объект с типом runnable, но теперь я использую одну строку кода, чтобы объявить реализацию одного абстрактного метода, а затем еще раз передаю объект в конструктор Thread. Вы все еще реализуете интерфейс runnable и вызываете его метод run, но вы делаете это с гораздо меньшим количеством кода. Кроме того, это может быть улучшено следующим образом:

    1
    2
    3
    4
    public static void main(String[] args) {  
             
           new Thread(() -> System.out.println("I am running")).start();
    }

    Вот важная цитата из раннего спецификационного документа о Project Lambda.

    Лямбда-выражения могут появляться только в тех местах, где они будут назначены переменной, тип которой является функциональным интерфейсом.
    Цитата Брайана Гетца

    Давайте разберемся с этим, чтобы понять, что происходит.

Каковы функциональные интерфейсы?

Функциональный интерфейс — это интерфейс, который имеет только один пользовательский абстрактный метод. То есть тот, который не унаследован от класса объекта. У Java много таких интерфейсов, таких как Runnable, Comparable, Callable, TimerTask и многие другие.

До Java 8 они были известны как Единый абстрактный метод или интерфейсы SAM . В Java 8 мы теперь называем их функциональными интерфейсами .

Синтаксис лямбда-выражения:

JDK8-SES-Р2-1

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

В этом примере это метод без аргументов, поэтому он представлен только круглыми скобками. Но если я реализую метод, который принимает аргументы, я бы просто дал имена аргументов. Я не должен объявлять их типы.

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

Если это всего лишь одна строка кода, как в этом примере, вам больше ничего не нужно. Чтобы реализовать тело метода с несколькими операторами, заключите их в фигурные скобки .

1
2
3
4
Runnable r = ( ) -> {
 System.out.println("Hello!");
 System.out.println("Lambda!");
   };

Лямбда Цели:

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

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

Определение и создание функционального интерфейса

Чтобы начать изучать лямбда-выражения, я создам новый функциональный интерфейс. Интерфейс с одним абстрактным методом, а затем я реализую этот интерфейс с помощью лямбда-выражения.

Вы можете использовать мой проект исходного кода «JavaSE8-Features», размещенный на github, для навигации по коду проекта.

JDK8-SES-P2-2

  1. Метод без аргументов, лямбда-реализация

    В моем исходном коде я фактически поместил интерфейс в собственный подпакет, заканчивающийся lambda.interfaces . И я назову интерфейс HelloInterface . Чтобы реализовать интерфейс с лямбда-выражением, он должен иметь единственный абстрактный метод. Я объявлю открытый метод, который возвращает void, и назову его doGreeting . Он не будет принимать никаких аргументов. Это все, что вам нужно сделать, чтобы создать интерфейс, который можно использовать с лямбда-выражениями. При желании вы можете использовать новую аннотацию, добавленную в Java SE 8, под названием « Функциональный интерфейс» .

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    /**
     *
     * @author mohamed_taman
     */
    @FunctionalInterface
    public interface HelloInterface {
          
        void doGreeting();
          
    }

    Теперь я готов создать новый класс UseHelloInterface в пакете lambda.impl , который будет создавать экземпляр моего функционального интерфейса ( HelloInterface ) следующим образом:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    /**
     * @author mohamed_taman
     */
       
    public class UseHelloInterface {
          
        public static void main(String[] args) {
              
            HelloInterface hello = ()-> out.println("Hello from Lambda expression");
              
            hello.doGreeting();
              
        }
    }

    Запустите файл и проверьте результат, он должен запуститься и вывести следующее.

    1
    2
    3
    4
    ------------------------------------------------------------------------------------
    --- exec-maven-plugin:1.2.1:exec (default-cli) @ Java8Features ---
    Hello from Lambda expression
    ------------------------------------------------------------------------------------

    Вот как может выглядеть код, когда вы работаете с одним абстрактным методом, который не принимает никаких аргументов. Давайте посмотрим, как это выглядит с аргументами.

  2. Метод с любым аргументом, лямбда-реализация

    Под лямбда.интерфейс . Я создам новый интерфейс и назову его CalculatorInterface . Затем я объявлю открытый метод, который возвращает void, и назову его doCalculate , который получит два целочисленных аргумента value1 и value2 .

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    /**
     * @author mohamed_taman
     */
      
    @FunctionalInterface
    public interface CalculatorInterface {
          
        public void doCalculate(int value1, int value2);
          
    }

    Теперь я готов создать новый класс Use CalculatorInterface в пакете lambda.impl , который создаст экземпляр моего функционального интерфейса ( CalculatorInterface ) следующим образом:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static void main(String[] args) {
             
           CalculatorInterface calc = (v1, v2) -> {
               int result = v1 * v2;
               out.println("The calculation result is: "+ result);
           };
             
           calc.doCalculate(10, 5);
       }

    Обратите внимание на аргументы doCalculate () , они были названы value1 и value2 в интерфейсе, но вы можете назвать их как угодно здесь. Я назову их v1 и v2. Мне не нужно вводить int перед именами аргументов; эта информация уже известна, потому что компилятор может вывести эту информацию из сигнатуры метода функционального интерфейса. Запустите файл и проверьте результат, он должен запустить и вывести следующее.

    1
    2
    3
    4
    5
    ------------------------------------------------------------------------------------
    --- exec-maven-plugin:1.2.1:exec (default-cli) @ Java8Features ---
    The calculation result is: 50
    ------------------------------------------------------------------------------------
    BUILD SUCCESS

    Всегда имейте в виду следующее правило:

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

  3. Использование встроенных функциональных интерфейсов с лямбдами

    Ранее я описывал, как использовать лямбда-выражение для реализации интерфейса, который вы создали сами. Теперь я покажу лямбда-выражения со встроенными интерфейсами. Интерфейсы, которые являются частью среды выполнения Java. Я буду использовать два примера. Я работаю в пакете под названием lambda.builtin , который является частью файлов упражнений. И я начну с этого класса. UseThreading . В этом классе я реализую интерфейс Runnable . Этот интерфейс является частью многопоточной архитектуры Java. Здесь я сосредоточен на том, как вы кодируете, а не на том, как он работает. Я собираюсь показать, как использовать лямбда-выражения для замены этих внутренних классов. Я закомментирую код, который объявляет два объекта. Затем я повторно объявлю их и сделаю реализацию с лямбдами. Итак, начнем.

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    public static void main(String[] args) {
           //Old version
             
    // Runnable thrd1 = new Runnable(){
      
    //  @Override
    //  public void run() {
    //    out.println("Hello Thread 1.");
    //  }
    //};
           /*
            *****************************************
            * Using lambda expression inner classes *
            *****************************************
            */
             
           Runnable thrd1 = () -> out.println("Hello Thread 1.");
      
           new Thread(thrd1).start();
      
            // Old Version
            /*
             new Thread(new Runnable() {
      
                @Override
                public void run() {
                    out.println("Hello Thread 2.");
                }
             }).start();
           */
      
           /*
            ******************************************
            * Using lambda expression anonymous class *
            ******************************************
            */
           new Thread(() -> out.println("Hello Thread 2.")).start();
      
       }

    Давайте посмотрим на другой пример. Я буду использовать Компаратор . Comparator — это еще один функциональный интерфейс в Java, который имеет единственный абстрактный метод. Этот метод является методом сравнения. Откройте файл класса UseComparator и проверьте закомментированный бит кода, который является фактическим кодом, прежде чем преобразовать его в лямбда-выражение.

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public static void main(String[] args) {
      
           List<string> values = new ArrayList();
           values.add("AAA");
           values.add("bbb");
           values.add("CCC");
           values.add("ddd");
           values.add("EEE");
      
           //Case sensitive sort operation
           sort(values);
      
           out.println("Simple sort:");
           print(values);
      
           // Case insensetive sort operation with anonymous class
     /*     
     Collections.sort(values, new Comparator<string>() {
      
              @Override
              public int compare(String o1, String o2) {
                  return o1.compareToIgnoreCase(o2);
              }
           });
    */
                     
           // Case insensetive sort operation with Lambda
           sort(values,(o1, o2) -> o1.compareToIgnoreCase(o2));
      
           out.println("Sort with Comparator");
           print(values);
       }

    Как и прежде, он не дает вам никакого выигрыша в производительности . Базовая функциональность точно такая же. Независимо от того, объявляете ли вы свои собственные классы , используете ли вы внутренние или анонимные внутренние классы или лямбда-выражения , все зависит от вас.

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

Ресурсы:

  1. Учебники по Java, лямбда-выражения
  2. JSR 310: API даты и времени
  3. JSR 337: Java SE 8 Release Содержание
  4. Сайт OpenJDK
  5. Платформа Java, стандартное издание 8, спецификация API