Java. Лямбда-выражения. Основные понятия. Функциональный интерфейс. Обобщенные функциональные интерфейсы и лямбда-выражения. Примеры




Java. Лямбда-выражения. Основные понятия. Функциональный интерфейс. Обобщенные функциональные интерфейсы и лямбда-выражения. Примеры


Содержание


Поиск на других ресурсах:

1. Понятие о лямбда-выражении. Преимущества применения лямбда-выражений

Лямбда-выражения появились в версии JDK 8 с целью усовершенствования языка Java. Лямбда-выражения еще называют замыканиями.

К преимуществам применения лямбда-выражений в языке Java можно отнести следующие:

  • использование новых элементов, повышающих выразительность языка Java. Упрощается реализация некоторых общеупотребительных конструкций;
  • расширение возможностей библиотеки программного интерфейса (API). Сюда можно отнести упрощение параллельной обработки, улучшение работы с потоками ввода/вывода в программном интерфейсе API.

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

 

2. Лямбда-выражение (замыкание). Общая форма. Лямбда-оператор ->. Примеры

 Реализация любого лямбда-выражения базируется на использовании двух языковых конструкций:

  • непосредственно лямбда-выражения;
  • функционального интерфейса.

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

При объявлении лямбда-выражения используется лямбда-оператор, который обозначается символами –> (стрелка). Лямбда-оператор трактуется как «становится» или «переходит». Лямбда-оператор (–>) разделяет лямбда-выражение на две части: левую и правую. В левой части лямбда-выражения указываются параметры. В правой части указываются действия (операторы), которые определяют код лямбда-выражения.

Код лямбда-выражения может формироваться одним из двух способов:

  • содержать одиночное выражение. В этом случае лямбда-выражение называется одиночным;
  • заключенный в фигурные скобки { }. Это случай, когда в правой части лямбда-оператора нужно указать несколько операторов. В этом случае лямбда-выражение называется блочным. При возврате результата в блочных лямбда-выражениях обязательно нужно указывать оператор return.

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

(list_of_parameters) -> {
  // Тело лямбда-выражения
  // ...
  return result;
}

здесь

  • list_of_parameters – список параметров, которые получает лямбда-выражение. Список параметров указывается через запятую точно также как в методе. Список параметров указывается в левой части лямбда-оператора. Параметры лямбда-выражения должны быть совместимы по типу и количеству с параметрами абстрактного метода, который объявлен в функциональном интерфейсе.

Если в лямбда-выражении содержится единственный параметр, он может быть без круглых скобок ( ):

i -> {
  // ...
}

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

() -> {
  // Тело лямбда-выражения
  // ...
}

Если в лямбда-выражении используется один оператор (выражение), то фигурные скобки можно опустить:

(list_of_parameters) -> expression;

здесь

  • list_of_parameters — список параметров метода;
  • expression – выражение, которое будет вычислено при использовании лямбда-выражения.

Пример 1. Ниже приведено лямбда-выражение без параметров, которое возвращает число π:

() -> Math.PI

В вышеприведенном коде результатом лямбда-выражения служит константа PI из класса Math. Можно было написать другое решение

() -> 3.1415

Пример 2. Лямбда-выражение, которое получает два целочисленных параметра и возвращает их произведение.

(int a, int b) -> a*b

Приклад 3. Лямбда-выражение, которое по длинам трех сторон a, b, c возвращает площадь треугольника.

(double a, double b, double c ) -> {
  if (((a+b)<c) || ((a+c)<b) || ((b+c)<a))
    return 0.0;
  else
  {
    double p = (a+b+c)/2; // полупериметер
    double s = Math.sqrt(p*(p-a)*(p-b)*(p-c)); // площадь по формуле Герона
    return s;
  }
}

 

3. Функциональный интерфейс. Определение. Общая форма. Примеры

Функциональный интерфейс — это интерфейс, который содержит только один абстрактный метод. Функциональный интерфейс определяет только одно действие (операцию). В функциональном интерфейсе определяется целевой тип лямбда-выражения. Функциональный интерфейс еще называют SAM-типом (Single Abstract Method).

В наиболее общем случае объявления функционального интерфейса выглядит следующим образом:

interface InterfaceName {
  return_type MethodName(list_of_parameters);
}

здесь

  • InterfaceName – имя функционального интерфейса;
  • MethodName – имя метода, который определяет назначение интерфейса;
  • return_type – тип, который возвращает метод MethodName;
  • list_of_parameters – список параметров метода MethodName.

Если в интерфейсе объявляется два и более абстрактных метода, то этот интерфейс не считается функциональным интерфейсом.

Пример 1. Объявляется функциональный интерфейс, который определяет абстрактный метод, не получающий параметров и возвращающий целое число

// Интерфейс, который определяет метод без параметров, который возвращает целое число
interface INumber {
  int GetNumber();
}

Код лямбда-выражения, реализующий данный интерфейс, может быть, например, следующим:

// Лямбда-выражение, реализующее интерфейс INumber
INumber lambdaNumber = () -> 255;

После формирования лямбда-выражения можно вызвать метод GetNumber() интерфейса INumber()

// Использование лямбда-выражения в виде метода GetNumber()
int number = lambdaNumber.GetNumber(); // number = 255
System.out.println("number = " + number);

Пример 2. Объявляется функциональный интерфейс, который определяет метод, получает 3 числа типа double и возвращает значение типа double

// Интерфейс, определяющий абстрактный метод, который получает 3 параметра
// типа double и возвращает значение типа double.
interface ICalculation {
  double CalcMethod(double a, double b, double c);
}

Для такой сигнатуры метода можно реализовывать лямбда-выражения, которые выполняют различные операции над тремя числами, например:

  • вычисляют сумму трех чисел;
  • вычисляют максимальное (минимальное) значение среди трех чисел и т.д.

Ниже приведено построение и использование лямбда-выражения, вычисляющего сумму трех чисел

// Построение лямбда-выражения для интерфейса ICalculation
// 1. Формирование метода CalcMethod(), который вычисляет
//   сумму 3-х чисел типа double с помощью лямбда-выражения
ICalculation ICalc = (a, b, c) -> a+b+c;

// 2. Вычислить сумму целых чисел 1+2+3
int sum = (int)ICalc.CalcMethod(1, 2, 3);
System.out.println("sum = " + sum);

Как видно из вышеприведенного кода, допускается использование целочисленных типов в методе, который оперирует типом double.

 

4. Последовательность шагов при построении лямбда-выражений

Для того, чтобы в программе использовать лямбда-выражения нужно выполнить следующие шаги:

  1. Объявить функциональный интерфейс I, содержащий только один абстрактный метод M. Сигнатура параметров метода M и тип возвращаемого результата должны соответствовать решаемой задаче.
  2. Объявить ссылку Ref на функциональный интерфейс I в методе, где нужно использовать лямбда-выражение.
  3. Сформировать код лямбда-выражения согласно синтаксису и присвоить этот код ссылке Ref на функциональный интерфейс. Количество параметров в лямбда-выражении должно совпадать с количеством параметров, которые объявлены в методе M функционального интерфейса I.
  4. С помощью ссылки Ref вызвать метод M. Вызов выглядит следующим образом:
Ref.M(parameters)

здесь parameters – список параметров метода M.

 

5. Решение задач на лямбда-выражения
5.1. Лямбда-выражение, которое обрабатывает три числа

Условие задачи. Создать и вызвать лямбда-выражение, которое вычисляет:

  • сумму трех чисел;
  • максимальное значение из трех чисел.

Задачу реализовать с использованием функционального интерфейса.

Решение. Текст программы следующий.

 

package TrainLambda;

// Объявить функциональный интерфейс
interface MyNumber {
  // Метод, получающий три параметра типа double
  // и возвращающий результат типа double
  double GetValue(double a, double b, double c);
}

public class TrainLambda01 {

  public static void main(String[] args) {
    // Использование функционального интерфейса MyNumber
    // 1. Объявить ссылку на функциональный интерфейс
    MyNumber mn;

    // 2. Присвоить ссылке mn лямбда-выражение, здесь создается
    //     экземпляр класса с реализацией метода GetValue().
    //     Ссылка mn есть ссылкой на этот экземпляр класса.
    mn=(a,b,c)->a+b+c; // одиночное лямбда-выражение

    // 3. Вызвать метод GetValue() и вывести результат
    double res = mn.GetValue(5, 7.5, 3.8);
    System.out.println("res = " + res);

    // --------------------------------
    // 4. Вычисление максимума из трех чисел
    //    Сформировать новое блочное лямбда-выражение
    mn=(a,b,c) -> {
      double max=a;
      if (max<b) max=b;
      if (max<c) max=c;
      return max;
    };

    // Вызов метода GetValue() и вывод результата
    double max = mn.GetValue(5.3, 8.3, 4.2);
    System.out.println("max = " + max);
  }
}

Результат выполнение программы

res = 16.3

 

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

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

  • определяют, является ли элемент массива парным;
  • определяют, является ли элемент массива непарным.

 

package TrainLambda;

// Функциональный интерфейс, который предназначен для проверки числа на четность.
// Этот интерфейс содержит только 1 метод, который получает целое число и
// возвращает результат логического типа.
interface Odd {
  // Абстрактный метод проверки на четность числа типа int
  boolean IsOdd(int d);
}

// Класс, содержащий метод, находящий сумму элементов массива
class SumArray {
  // Метод, вычисляющий сумму элементов массива.
  // При вычислении суммы учитывается результат возврата из лямбда-выражения.
  // Метод получает 2 параметра:
  // - A - суммируемый массив;
  // - refLambda - ссылка на функциональный интерфейс Odd.

  public int Sum(int[] A, Odd refLambda) {
    int sum=0;
    for (int i=0; i<A.length; i++)
      if (refLambda.IsOdd(A[i])) // четный ли элемент массива?
        sum+=A[i];
    return sum;
  }
}

public class TrainLambda01 {

  public static void main(String[] args) {
    // Передача лямбда-выражения в метод как аргумента.
    // 1. Объявить ссылку на интерфейс Odd
    Odd ref1;

    // 2. Присвоить ссылке ref1 лямбда-выражение
    ref1 = (d) -> {
      // код проверки на четность
      if (d%2==0) return true;
      else return false;
    };

    // 3. Продемонстрировать вызов метода Sum класса SumArray и передачу ему лямбда-выражения
    // 3.1. Создать экземпляр класса SumArray
    SumArray obj1 = new SumArray();

    // 3.2. Объявить массив целых чисел
    int[] A = { 5, 3, 8, 10, 2, 1, 2, 3 };

    // 3.3. Вызвать метод вычисления суммы элементов массива
    //      с лямбда-выражением, которое выбирает только парные элементы
    int sum = obj1.Sum(A, ref1);
    System.out.println("sum = " + sum);

    // 3.4. Передача в метод лямбда-выражения для непарных элементов
    sum = obj1.Sum(A,
    (Odd)((d) -> {
      if (d%2!=0) return true;
      else return false;
    }));
    System.out.println("sum = " + sum);
  }
}

Результат выполнения программы

sum = 22
sum = 12

В вышеприведенном коде можно использовать ссылку на метод по образцу:

имя_экземпляра::имя_метода

В программе передача метода IsOdd() в метод Sum() будет выглядеть следующим образом:

// также работает - передаеться ссылка на метод экземпляра
int sum = obj1.Sum(A, ref1::IsOdd);

 

5.3. Лямбда-выражение, использующее шаблон (обобщение)

Создать и вызвать лямбда-выражение используя шаблон (обобщение). Лямбда-выражение должно находить максимум из трех цифр.

 

package TrainLambda;

// Обобщенный (шаблонный) функциональный интерфейс.
// Содержит метод, получающий 3 числа типа T и
// возвращающий значение типа T.
interface Max3<T> {
  T Max(T a, T b, T c);
}

public class TrainLambda02 {

  public static void main(String[] args) {
    // Использовение интерфейса Max3<T>
    // 1. Объявить ссылку на интерфейс Max3<T>
    Max3<Integer> refInt;

    // 2. Создать лямбда-выражение с привязкой к типу int
    refInt = (a, b, c) -> {
      Integer max = a;
      if (max<b) max=b;
      if (max<c) max=c;
        return max;
    };

    // 3. Вызвать лямбда выражение для трех целых чисел
    int res = refInt.Max(5, 7, 3); // res = 7
    System.out.println("res = " + res);

    // 4. Создать лямбда-выражение с привязкой к типу double
    Max3<Double> refDouble;
    refDouble = (a, b, c) -> {
      Double max = a;
      if (max<b) max=b;
      if (max<c) max=c;
      return max;
    };

    // 5. Вызвать лямбда-выражение с привязкой к типу double
    double resMax = refDouble.Max(3.88, 2.55, 4.11);
    System.out.println("resMax = " + resMax);
  }
}

 

5.4. Решение квадратного уравнения с помощью лямбда-выражения

В задаче демонстрируется использование лямбда-выражения для решения квадратного уравнения. Программа содержит следующие составляющие:

  • функциональный интерфейс Equation. В интерфейсе объявляется один метод, получающий три параметра типа double. Эти параметры являются коэффициентами a, b, c квадратного уравнения. Метод возвращает значение корней уравнения в виде экземпляра класса Solution. Если уравнение не имеет решения, то метод возвращает null;
  • класс Solution — содержит два поля x1, x2 типа double, которые являются корнями квадратного уравнения;
  • класс TrainLambda02, содержащий функцию main(). В этой функции выполняется построение и использование лямбда-выражения для решения квадратного уравнения для значений a = 2, b = 8, c = 4.

Текст программы следующий:

// Функциональный интерфейс для решения квадратного уравнения.
interface Equation {
  // Метод, получающий параметры - коэффициенты уравнения a*x^2 + b*x + c = 0.
  // Если уравнение не имеет решения, то метод возвращает null.
  // В другом случае, метод возвращает экземпляр класса Solution
  // с корнями уравнения x1, x2.
  Solution CalcEquation(double a, double b, double c);
}

// Результат решения квадратного уравнения
class Solution {
  double x1;
  double x2;
}

public class TrainLambda02 {

  public static void main(String[] args) {
    // Лямбда-выражение, решающее квадратное уравнение.
    // Результат возвращается в экземпляре класса Solution.
    // 1. Объявить ссылку на функциональный интерфейс
    Equation eq;

    // 2. Присвоить ссылке лямбда-выражение, решающее
    //   квадратное уравнение
    eq = (double a, double b, double c) -> {
      double d = b*b - 4*a*c; // дискриминант

      if (d>=0) {
        // Найти корни
        Solution roots = new Solution();
        roots.x1 = (-b-Math.sqrt(d))/(2*a);
        roots.x2 = (-b+Math.sqrt(d))/(2*a);
        return roots;
      }
      else
        return null;
    };

    // 3. Решить квадратное уравнение 2*x^2 - 8*x + 4 = 0
    Solution roots = eq.CalcEquation(2, -8, 4);
    if (roots==null)
      System.out.println("The solution has no roots.");
    else
    {
      System.out.println("x1 = " + roots.x1);
      System.out.println("x2 = " + roots.x2);
    }
  }
}

Результат выполнения программы

x1 = 0.5857864376269049
x2 = 3.414213562373095

 


Связанные темы