- Как начать работать с Лямбда-выражениями в Java
- Краткое введение
- Функциональный интерфейс
- Оператор Стрелка
- Блок Лямбда-выражений
- Функциональные интерфейсы generic
- Использование Лямбда-выражений в качестве аргументов
- Лямбда-выражения
- Отложенное выполнение
- Передача параметров в лямбда-выражение
- Терминальные лямбда-выражения
- Лямбды и локальные переменные
- Блоки кода в лямбда-выражениях
- Обобщенный функциональный интерфейс
Как начать работать с Лямбда-выражениями в Java
Привет, Хабр! Представляю вашему вниманию перевод статьи «How to start working with Lambda Expressions in Java» автора Luis Santiago.
До того как Лямбда-выражения были добавлены в JDK 8, я использовал их в таких языках как C# и С++. Когда они были добавлены в Java я стал изучать их подробнее.
С добавлением Лямбда-выражений добавились элементы синтаксиса, которые увеличивают «выразительную силу» Java. В этой статье я хочу сосредоточиться на основополагающих концепциях, с которыми вам необходимо познакомиться, чтобы начать использовать Лямбда-выражения.
Краткое введение
Лямбда-выражения используют преимущества параллельных процессов в многоядерных средах, что видно при поддержке операций с конвейерами данных в Stream API.
Это анонимные методы (методы без имени), используемые для реализации метода, определенного функциональным интерфейсом. Важно знать, что такое функциональный интерфейс, прежде чем вы начнете использовать Лямбда-выражения.
Функциональный интерфейс
Функциональный интерфейс — это интерфейс, содержащий один и только один абстрактный метод.
Если вы посмотрите на определение стандартного интерфейса Runnable, то вы заметите как он попадает в определение функционального интерфейса, поскольку он определяет только один метод: run().
В приведенном ниже примере кода метод computeName является абстрактным и единственным методом в интерфейсе MyName , что делает его функциональным интерфейсом.
Оператор Стрелка
Лямбда-выражения вводят новый оператор стрелка -> в Java. Он разделяет лямбда-выражение на 2 части:
В левой части задаются параметры, необходимые для выражения. Эта часть может быть пустой если не требуется никаких параметров.
Правая сторона — это тело выражения, которое определяет его действия.
Теперь, используя функциональные выражения и оператор стрелки можно составить просто лямбда-выражение:
interface NumericTest < boolean computeTest(int n); >public static void main(String args[]) < NumericTest isEven = (n) ->(n % 2) == 0; NumericTest isNegative = (n) -> (n < 0); // Output: false System.out.println(isEven.computeTest(5)); // Output: true System.out.println(isNegative.computeTest(-5)); >
interface MyGreeting < String processName(String str); >public static void main(String args[]) < MyGreeting morningGreeting = (str) ->"Good Morning " + str + "!"; MyGreeting eveningGreeting = (str) -> "Good Evening " + str + "!"; // Output: Good Morning Luis! System.out.println(morningGreeting.processName("Luis")); // Output: Good Evening Jessica! System.out.println(eveningGreeting.processName("Jessica")); >
Переменные morningGreeting и eveningGreeting в строках 6 и 7 соответственно в примере выше создают ссылку на интерфейс MyGreeting и определяют 2 выражения приветствия.
При написании лямбда-выражения можно явно указать тип параметра, как это делается в примере ниже:
MyGreeting morningGreeting = (String str) -> "Good Morning " + str + "!"; MyGreeting eveningGreeting = (String str) -> "Good Evening " + str + "!";
Блок Лямбда-выражений
До сих пор я рассматривал одиночные лямбда-выражения. Существует еще один тип выражения, когда справа от оператора стрелки находится не одно простое выражение и так называемый блок лямбда:
interface MyString < String myStringFunction(String str); >public static void main (String args[]) < // Block lambda to reverse string MyString reverseStr = (str) ->< String result = ""; for(int i = str.length()-1; i >= 0; i--) result += str.charAt(i); return result; >; // Output: omeD adbmaL System.out.println(reverseStr.myStringFunction("Lambda Demo")); >
Функциональные интерфейсы generic
Лямбда-выражения не могут быть generic, но функциональный интерфейс, связанный с выражением, может. Можно написать один общий интерфейс и возвращать различные типы данных, например:
interface MyGeneric < T compute(T t); >public static void main(String args[]) < // String version of MyGenericInteface MyGenericreverse = (str) -> < String result = ""; for(int i = str.length()-1; i >= 0; i--) result += str.charAt(i); return result; >; // Integer version of MyGeneric MyGeneric factorial = (Integer n) -> < int result = 1; for(int i=1; i ; // Output: omeD adbmaL System.out.println(reverse.compute("Lambda Demo")); // Output: 120 System.out.println(factorial.compute(5)); >
Использование Лямбда-выражений в качестве аргументов
Одно распространенное использование лямбда — передача их в качестве аргументов.
Вы можете передавать исполняемый код аргументам методов в качестве параметров. Для этого просто убедитесь, что тип функционального интерфейса совместим с требуемым параметром.
interface MyString < String myStringFunction(String str); >public static String reverseStr(MyString reverse, String str) < return reverse.myStringFunction(str); >public static void main (String args[]) < // Block lambda to reverse string MyString reverse = (str) ->< String result = ""; for(int i = str.length()-1; i >= 0; i--) result += str.charAt(i); return result; >; // Output: omeD adbmaL System.out.println(reverseStr(reverse, "Lambda Demo")); >
Эти концепции дадут вам хорошую основу для начала работы с лямбда-выражениями. Взгляните на свой код и посмотрите, где вы можете их использовать.
Лямбда-выражения
Среди новшеств, которые были привнесены в язык Java с выходом JDK 8, особняком стоят лямбда-выражения. Лямбда представляет набор инструкций, которые можно выделить в отдельную переменную и затем многократно вызвать в различных местах программы.
Основу лямбда-выражения составляет лямбда-оператор , который представляет стрелку -> . Этот оператор разделяет лямбда-выражение на две части: левая часть содержит список параметров выражения, а правая собственно представляет тело лямбда-выражения, где выполняются все действия.
Лямбда-выражение не выполняется само по себе, а образует реализацию метода, определенного в функциональном интерфейсе . При этом важно, что функциональный интерфейс должен содержать только один единственный метод без реализации.
public class LambdaApp < public static void main(String[] args) < Operationable operation; operation = (x,y)->x+y; int result = operation.calculate(10, 20); System.out.println(result); //30 > > interface Operationable
В роли функционального интерфейса выступает интерфейс Operationable , в котором определен один метод без реализации — метод calculate . Данный метод принимает два параметра — целых числа, и возвращает некоторое целое число.
По факту лямбда-выражения являются в некотором роде сокращенной формой внутренних анонимных классов, которые ранее применялись в Java. В частности, предыдущий пример мы можем переписать следующим образом:
public class LambdaApp < public static void main(String[] args) < Operationable op = new Operationable()< public int calculate(int x, int y)< return x + y; >>; int z = op.calculate(20, 10); System.out.println(z); // 30 > > interface Operationable
Чтобы объявить и использовать лямбда-выражение, основная программа разбивается на ряд этапов:
- Определение ссылки на функциональный интерфейс:
int result = operation.calculate(10, 20);
При этом для одного функционального интерфейса мы можем определить множество лямбда-выражений. Например:
Operationable operation1 = (int x, int y)-> x + y; Operationable operation2 = (int x, int y)-> x - y; Operationable operation3 = (int x, int y)-> x * y; System.out.println(operation1.calculate(20, 10)); //30 System.out.println(operation2.calculate(20, 10)); //10 System.out.println(operation3.calculate(20, 10)); //200
Отложенное выполнение
Одним из ключевых моментов в использовании лямбд является отложенное выполнение (deferred execution). То есть мы определяем в одном месте программы лямбда-выражение и затем можем его вызывать при необходимости неопределенное количество раз в различных частях программы. Отложенное выполнение может потребоваться, к примеру, в следующих случаях:
- Выполнение кода отдельном потоке
- Выполнение одного и того же кода несколько раз
- Выполнение кода в результате какого-то события
- Выполнение кода только в том случае, когда он действительно необходим и если он необходим
Передача параметров в лямбда-выражение
Параметры лямбда-выражения должны соответствовать по типу параметрам метода из функционального интерфейса. При написании самого лямбда-выражения тип параметров писать необязательно, хотя в принципе это можно сделать, например:
Если метод не принимает никаких параметров, то пишутся пустые скобки, например:
Если метод принимает только один параметр, то скобки можно опустить:
Терминальные лямбда-выражения
Выше мы рассмотрели лямбда-выражения, которые возвращают определенное значение. Но также могут быть и терминальные лямбды, которые не возвращают никакого значения. Например:
interface Printable < void print(String s); >public class LambdaApp < public static void main(String[] args) < Printable printer = s->System.out.println(s); printer.print("Hello Java!"); > >
Лямбды и локальные переменные
Лямбда-выражение может использовать переменные, которые объявлены во вне в более общей области видимости — на уровне класса или метода, в котором лямбда-выражение определено. Однако в зависимости от того, как и где определены переменные, могут различаться способы их использования в лямбдах. Рассмотрим первый пример — использования переменных уровня класса:
public class LambdaApp < static int x = 10; static int y = 20; public static void main(String[] args) < Operation op = ()->< x=30; return x+y; >; System.out.println(op.calculate()); // 50 System.out.println(x); // 30 — значение x изменилось > > interface Operation
Переменные x и y объявлены на уровне класса, и в лямбда-выражении мы их можем получить и даже изменить. Так, в данном случае после выполнения выражения изменяется значение переменной x.
Теперь рассмотрим другой пример — локальные переменные на уровне метода:
public static void main(String[] args) < int n=70; int m=30; Operation op = ()->< //n=100; - так нельзя сделать return m+n; >; // n=100; - так тоже нельзя System.out.println(op.calculate()); // 100 >
Локальные переменные уровня метода мы также можем использовать в лямбдах, но изменять их значение нельзя. Если мы попробуем это сделать, то среда разработки (Netbeans) может нам высветить ошибку и то, что такую переменную надо пометить с помощью ключевого слова final , то есть сделать константой: final int n=70; . Однако это необязательно.
Более того, мы не сможем изменить значение переменной, которая используется в лямбда-выражении, вне этого выражения. То есть даже если такая переменная не объявлена как константа, по сути она является константой.
Блоки кода в лямбда-выражениях
Существуют два типа лямбда-выражений: однострочное выражение и блок кода. Примеры однострочных выражений демонстрировались выше. Блочные выражения обрамляются фигурными скобками. В блочных лямбда-выражениях можно использовать внутренние вложенные блоки, циклы, конструкции if, switch, создавать переменные и т.д. Если блочное лямбда-выражение должно возвращать значение, то явным образом применяется оператор return :
Operationable operation = (int x, int y)-> < if(y==0) return 0; else return x/y; >; System.out.println(operation.calculate(20, 10)); //2 System.out.println(operation.calculate(20, 0)); //0
Обобщенный функциональный интерфейс
Функциональный интерфейс может быть обобщенным, однако в лямбда-выражении использование обобщений не допускается. В этом случае нам надо типизировать объект интерфейса определенным типом, который потом будет применяться в лямбда-выражении. Например:
public class LambdaApp < public static void main(String[] args) < Operationableoperation1 = (x, y)-> x + y; Operationable operation2 = (x, y) -> x + y; System.out.println(operation1.calculate(20, 10)); //30 System.out.println(operation2.calculate(«20», «10»)); //2010 > > interface Operationable
Таким образом, при объявлении лямбда-выражения ему уже известно, какой тип параметры будут представлять и какой тип они будут возвращать.