Вы находитесь на странице: 1из 29

ЛЯМБДА-ВЫРАЖЕНИЯ

Пример 1

public class MyLambda {


public static void main(String[] args) {

new Thread(new Runnable() {


@Override
public void run() {
System.out.println(“hello!”);
}
}).run();
}
}
Пример 2

public interface Incrementer {


int increment(int x);
}

public class Lambda {


public static void main(String[] args) {
Incrementer inc = new Incrementer() {
@Override
public int increment(int x) {
return x+1;
}
};
System.out.println(inc.increment(1));
}
}
Анонимный класс — это внутренний класс без имени, у которого
есть доступ к переменным внешнего класса, в том числе к
статическим и приватным.
Анонимные внутренние классы предоставляют встроенную
реализацию интерфейса или подкласс базового класса, который
вы хотите использовать лишь в одной точке своего программного
кода.
Если имя, следующее за ключевым словом new, это имя класса,
то анонимный класс является подклассом этого класса. Если имя,
следующее за ключевым словом new, представляет собой
интерфейс, то анонимный класс реализует этот интерфейс и
расширяет класс Object.
Лямбда представляет набор инструкций, которые можно
выделить в отдельную переменную и затем многократно вызвать
в различных местах программы.
Основу лямбда-выражения составляет лямбда-оператор, который
представляет стрелку ->. Этот оператор разделяет лямбда-
выражение на две части: левая часть содержит список
параметров выражения, а правая собственно представляет тело
лямбда-выражения, где выполняются все действия.

(параметры) -> (тело)

Например:
(пар1, пар2...) -> { тело }

(тип1 пар1, тип2 пар2...) -> { тело }


Тело лямбда-выражения может быть однострочным или
содержать в себе блок кода (циклы, конструкции if, switch, новые
переменные и т.д.)

Вот как выглядят реальные лямбда-выражения:


(int a, int b) -> { return a + b; }

() -> System.out.println("Hello World");

(String s) -> { System.out.println(s); }

() -> 42
() -> { return 3.1415 };
Cтруктура lambda-выражений:
 Lambda-выражения могут иметь от 0 и более входных
параметров.
 Тип параметров можно указывать явно либо может быть
получен из контекста. Например (int a) можно записать и так
(a)
 Параметры заключаются в круглые скобки и разделяются
запятыми. Например (a, b) или (int a, int b) или (String a, int
b, float c)
 Если параметров нет, то нужно использовать пустые круглые
скобки. Например () -> 42
 Когда параметр один, если тип не указывается явно, скобки
можно опустить. Пример: a -> return a*a
 Тело Lambda-выражения может содержать от 0 и более
выражений.
 Если тело состоит из одного оператора, его можно не
заключать в фигурные скобки, а возвращаемое значение
можно указывать без ключевого слова return.
 В противном случае фигурные скобки обязательны (блок
кода), а в конце надо указывать возвращаемое значение с
использованием ключевого слова return (в противном случае
типом возвращаемого значения будет void).
 Терминальные лямбды – не возвращают никакого значения.
Пример:
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!");
}
}
Лямбда-выражение не выполняется само по себе, а образует
реализацию метода, определенного в функциональном
интерфейсе.

Runnable – это функциональный интерфейс. В нем объявлен только один


метод void run()
Incrementer – это функциональный интерфейс с одним методом
int increment(int x);
В JDK 8 вместе с самой функциональностью лямбда-выражений
также было добавлено некоторое количество встроенных
функциональных интерфейсов:

Predicate<T>
Consumer<T>
Function<T,R>
Supplier<T>
UnaryOperator<T>/BinaryOperator<T>
Функциональный интерфейс в Java – это интерфейс, который содержит
только 1 абстрактный метод, а так же может содержать default и static
методы.
Основное назначение – использование в лямбда-выражениях и method
reference.
Рекомендуется добавлять @FunctionalInterface. Это не обязательно, но
при наличии данной аннотации код не скомпилируется, если будет
больше или меньше, чем 1 абстрактный метод (т.е. позволит
использовать интерфейс в лямбда-выражениях, не остерегаясь того, что
кто-то добавит в интерфейс новый абстрактный метод и он перестанет
быть функциональным).
В Java есть встроенные функциональные интерфейсы, размещенные в
пакете java.util.function

Помните, не важно сколько у вас методов по умолчанию или


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

public interface Operationable {


int calculate(int x, int y);
}
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
}
}
Объявление и использование лямбда-выражение
1. Определение ссылки на функциональный интерфейс:
Operationable operation;
2. Создание лямбда-выражения:
operation = (x,y)->x+y;
Параметры лямбда-выражения соответствуют параметрам
единственного метода интерфейса Operationable, а результат
соответствует возвращаемому результату метода интерфейса.
При этом нам не надо использовать ключевое слово return для
возврата результата из лямбда-выражения.
3. Использование лямбда-выражения в виде вызова метода
интерфейса:
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
Использование существующих методов в качестве лямбда-
выражений

Если существующий у вас метод уже делает все, что вам нужно, то
вы можете воспользоваться механизмом method reference
(ссылка на метод) для непосредственной передачи этого метода.
Работают ссылки на методы при условии, что параметры
вызываемого метода и параметры в лямбде совпадают.
Для использования необходимо написать оператор ::

Ссылка на метод передается в виде:


имя_класса::имя_статического_метода (если метод статический)
объект_класса::имя_метода (если метод нестатический)
Пример 4

Собственный метод и лямбда

public class MainClass {


public static void main(String[] args) {
Function<String, Integer> toInteger = string -> parse(string);
Integer integer = toInteger.apply("5");
}

private static Integer parse(String s) {


return Integer.parseInt(s);
}
}
Собственный метод и ссылка на метод

public class MainClass {


public static void main(String[] args) {
Function<String, Integer> toInteger = MainClass::parse;
Integer integer = toInteger.apply("5");
}

private static Integer parse(String s) {


return Integer.parseInt(s);
}
}

Еще примеры:
System.out::println эквивалентно x -> System.out.println(x)
Math::pow эквивалентно (x, y) -> Math.pow(x, y)
String::compareToIgnoreCase (x, y) -> x.compareToIgnoreCase(y)
Помимо методов, так же можно передавать ссылки на
конструкторы.

Пример 5

Для этого создадим класс User

class User {
String name, surname;

User(String name, String surname) {


this.name = name;
this.surname = surname;
}
public String getName() {
return name;
}
public String getSurname() {
return surname;
}
}

и интерфейс UserFactory

interface UserFactory {
User create(String name, String surname);
}

Теперь же, вместо реализации интерфейса используем ссылку на


конструктор User

public static void main(String[] args) {


UserFactory userFactory = User::new;
User user = userFactory.create("John", "Snow");
System.out.println(user.getName() + " " + user.getSurname());}
Лямбды как параметры методов

Одним из преимуществ лямбд в java является то, что их можно


передавать в качестве параметров в методы.

Пример 6

interface Expression{
boolean isEqual(int n);
}

public class LambdaApp {

public static void main(String[] args) {

Expression func = (n)-> n%2==0;


int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
System.out.println(sum(nums, func)); // 20
}
private static int sum (int[] numbers, Expression func) {
int result = 0;
for(int i : numbers)
{
if (func.isEqual(i))
result += i;
}
return result;
}
}

1. Функциональный интерфейс Expression определяет метод isEqual(),


который возвращает true, если в отношении числа n действует какое-
нибудь равенство.
2. В основном классе программы определяется метод sum(), который
вычисляет сумму всех элементов массива, соответствующих
некоторому условию:

private static int sum (int[] numbers, Expression func) {


//код
}
где int[] numbers – заданный массив
Expression func – параметр, содержащий в себе лямбда-выражение
с каким-либо условием (в нашем случае проверка на четность)

3. Условие передается через параметр Expression func. Причем на


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

Expression func = (n)-> n%2==0;


то есть в данном случае все числа должны быть четными или остаток от
их деления на 2 должен быть равен 0.

4. При этом можно не определять переменную интерфейса Expression


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

private static int sum (int[] numbers, (n)-> n%2==0) {


//код
}
Обобщенный функциональный интерфейс

Указывать параметр типа в лямбда-выражении нельзя, поэтому


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

public class LambdaApp {

public static void main(String[] args) {

Operationable<Integer> operation1 = (x, y)-> x + y;


Operationable<String> operation2 = (x, y) -> x + y;

System.out.println(operation1.calculate(20, 10)); //30


System.out.println(operation2.calculate("20", "10")); //2010
}
}
interface Operationable<T>{
T calculate(T x, T y);
}
Лямбды и локальные переменные

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


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

Использования переменных уровня класса

Пример 8

interface Operation{
int calculate();
}
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 изменилось
}
}
Переменные x и y объявлены на уровне класса, и в лямбда-
выражении мы их может получить и даже изменить. Так, в
данном случае после выполнения выражения изменяется
значение переменной x.

Локальные переменные на уровне метода

Пример 9

interface Operation{
int calculate();
}

public class LambdaApp {


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

Локальные переменные уровня метода мы также можем


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