Лямбда выражения
План лекции
Проблема
Вариант 1 – создание методов
Недостатки подхода
Вариант 2 – выделение проверки и операции в отдельные классы
Реализация с использованием локальных классов
Реализация с использованием анонимных классов
Особенности подхода
Вариант 2 – использование лямбда выражений
Что есть «лямбда-выражение»?
Лямбда-выражения в Java
Синтаксис Лямбда-выражений
Тип лямбда-выражений
Пример
Пример
Пример
Разрешение неоднозначности
Разрешение неоднозначности
Доступ к внешним переменным
Доступ к внешним переменным
Доступ к внешним переменным
Сериализация
Пример сериализации
Ссылки на методы
Пример
Пример
Пример
Пример
Пакет java.util.function
Агрегатные операции остались за бортом
Дополнительные источники
270.50K
Category: programmingprogramming

Лямбда выражения

1. Лямбда выражения

© Составление, Стефанов М.А., 2016
Лекция 10.1
УНЦ «Инфоком»
Самара
2018

2. План лекции

Понятие и синтаксис лямбда выражений
Контекст выполнения
Ссылки на методы
2

3. Проблема

Разрабатывается приложение учета и контроля ТС подземного паркинга
Допустим, информация по ТС содержится в коллекции List<Vehicle>
Приложение должно осуществлять поиск ТС по нескольким критериям и
выполнять различные операции над ними
class Vehicle {
private final String plate;
private final String model;
private final int stallNumber;
private final Person owner;

public String getPlate() {…}
public String getModel() {…}
public int getStallNumber() {…}
public Person getOwner() {…}

public void printVehicle() {…}
}
final class Person {
public final String firstName;
public final String secondName;
public final Address address;

public boolean isFlatOwner() {…}
public void printPerson(){…}
}
final class Address {
public final String city;
public final String street;
public final int flatNumber;
public final int houseNumber;

public void printAddress(){…}
}
3

4. Вариант 1 – создание методов

Создается куча методов, каждый из которых осуществляют
поиск ТС по заданным характеристикам и выполняет
некоторое действие над ними. Например,
public static void printVehicleWithStallNumbersInRange(
List<Vehicle> vehicles, int low, int high) {
for (Vehicle v : vehicles) {
if (low <= v.getStallNumber() && v.getStallNumber() <= high)
v.printVehicle();
}
}
4

5. Недостатки подхода

Придется переписывать весь (почти) API, если
поменяли структуру класса Vehicle
поменяли типы данных
Каждый метод представляет только одну комбинацию
критериев и действий. А если пользователю нужно дать
возможность произвольно выбирать критерии и
действия?
Придется создать по методу на каждую возможную
комбинацию
5

6. Вариант 2 – выделение проверки и операции в отдельные классы

interface Predicate<T> {
boolean test(T t);
}
interface Consumer<T> {
void accept(t);
}
public static void printVehicles (List<Vehicle> vehicles,
Predicate<Vehicle> criteria,
Consumer<Vehicle> consumer) {
for (Vehicle v : vehicles) {
if (criteria.test(v))
consumer.accept(v);
}
}
6

7. Реализация с использованием локальных классов

class PrintStallNumberModelAndFlatNumberConsumer
implements Consumer<Vehicle> {
public void accept(Vehicle vehicle) {
vehicle.printVehicle();
}
}
class FlatOwnerAndFirstFloorStallNumbers implements Predicate<Vehicle> {
public boolean test(Vehicle vehicle) {
return (0 <= vehicle.getStallNumber() &&
vehicle.getStallNumber() <= 5) &&
vehicle.getOwner().isFlatOwner();
}
}
printVehicles(vehicles,
new FlatOwnerAndFirstFloorStallNumbers(),
new PrintStallNumberModelAndFlatNumberConsumer());
7

8. Реализация с использованием анонимных классов

printVehicles(vehicles,
new Predicate<Vehicle>() {
public boolean test(Vehicle vehicle) {
return (0 <= vehicle.getStallNumber() &&
vehicle.getStallNumber() <= 5) &&
vehicle.getOwner().isFlatOwner();
}
},
new Consumer<Vehicle>() {
public void accept(Vehicle vehicle) {
vehicle.printVehicle();
}
});
8

9. Особенности подхода

Код не зависит от структуры и типов данных класса
Vehicle
Клиенты будут сами использовать нужные критерии и
операции, определяя классы, реализующие интерфейсы
Predicate<Vehicle> и Consumer<Vehicle>
По-прежнему много кода – определений классов или
сложные конструкции при использовании анонимных
классов
9

10. Вариант 2 – использование лямбда выражений

Поскольку интерфейсы содержат только один метод,
можем использовать лямбда выражения
Отсутствует код определения классов и методов, есть
только тело метода
Выражения получаются компактнее
printVehicles(vehicles,
vehicle -> 0 <= vehicle.getStallNumber() &&
vehicle.getStallNumber() <= 5 &&
vehicle.getOwner().isFlatOwner(),
vehicle -> System.out.printf("%-3d%-15s%d\n",
vehicle.getStallNumber(),
vehicle.getModel(),
vehicle.getOwner().address.flatNumber));
10

11. Что есть «лямбда-выражение»?

В основе лежит лямбда-исчисление Черча –
нотация определения функций
Лямбда-выражение – определение функции без
имени (анонимная функция), состоящее из
списка формальных параметров и тела функции
В функциональном программировании
различают лямбда-выражение и лямбда-вызов
(то же лямбда-выражение но с фактическими
параметрами)
11

12. Лямбда-выражения в Java

В основе лежит функциональный интерфейс
Функциональный интерфейс – интерфейс с
только одним абстрактным методом
Функциональный интерфейс может содержать
так же static и default методы и другие
стандартные элементы
Имя абстрактного метода не важно
Лямбда-выражение – определение абстрактного
метода функционального интерфейса
12

13. Синтаксис Лямбда-выражений

список_параметров -> тело_абстрактного_метода;
список_параметров – разделенные запятой пары
«тип имя», заключенные в круглые скобки
тип можно не писать, если компилятор может
определить тип параметра из контекста вызова
если есть только один параметр, скобки можно не
писать (при условии, что тип тоже не пишется)
тело_абстрактного_метода – обычный блок кода
(заключенные в фигурные скобки инструкции)
фигурные скобки и оператор return можно не писать,
если тело метода – одно выражение или вызов метода
13

14. Тип лямбда-выражений

Лямбда выражение можно рассматривать как некоторый
объект, реализующий функциональный интерфейс
Компилятор определяет целевой тип лямбда выражения
исходя из контекста его применения:
Определение переменной
Операторы присваивания
Оператор return
Инициализаторы массивов
Параметры методов и конструкторов
Тело другого лямбда выражения
Тернарный оператор «?:»
Выражения приведения (cast expression)
14

15. Пример

public interface Function<T, R> {
R apply(T t);
}
public interface Predicate<T> {
boolean test(T t);
}
public interface PrimitiveMath {
int DEFAULT_VALUE = 0;
int apply(int a, int b);
default int sum (int a, int b){
return a + b;
}
}
public class LambdaUserClass {
Function<String, Integer> strToIntConverter =
s -> Integer.valueOf(s);
Predicate<Integer> criteria;
PrimitiveMath intCalculator;
}
public LambdaUserClass(Predicate<Integer> criteria){
this.criteria = criteria;
}
15

16. Пример

//связывание локальной переменной с лямбда-выражением
Predicate<Integer> criteria =
(value) -> {if (value < 0)
throw new IllegalArgumentException();
else return (value < 10);};
LambdaUserClass class1 = new LambdaUserClass(criteria);
//связывание поля класса с лямбда-выражением
class1.intCalculator = (a, b) -> (a + b);
System.out.println(class1.intCalculator.apply(10, 20));
=>
30
//связывание того же поля класса с другим лямбда-выражением
class1.intCalculator = (a, b) -> {if (criteria.test(a))
return (a + b);
else return (a - b); };
System.out.println(class1.intCalculator.apply(30, 20));
=>
10
16

17. Пример

public interface Consumer<T> {
void accept(T t);
}
public interface Replicator<T> {
T replicate(T t);
}
//передача лямбда-выражения в качестве фактического параметра
конструктора
LambdaUserClass class2 = new LambdaUserClass(value -> value < 0);
//использование лямбда-выражений в инициализаторе массива
Consumer[] IntConsumers = new Consumer[]
{value -> System.out.println(value),
value -> System.out.printf("value is: %s\n", value),
value -> {if (value instanceof Consumer)
System.out.println("Consuming myself");}};
//Вложенные лямбда-выражения и возврат выражения через оператор return
Replicator<Replicator<Integer>> replicator =
(repl) -> {return (rep) -> {Integer r;
r = (Integer) rep.clone();
return r;};};
17

18. Разрешение неоднозначности

interface IntegerMath {
int apply(int a, int b);
}
interface IntegerMath2 {
int apply(int a, int b);
}
interface DoubleMath {
double apply(double a, double b);
}
public class TargetTypingExample {
public void overload (IntegerMath math){
System.out.println("Integer " + math.apply(10, 20));
}
public void overload (DoubleMath math){
System.out.println("Float " + math.apply(10.0, 20.0));
}
public void overload2 (IntegerMath math){
System.out.println("Integer " + math.apply(10, 20));
}
public void overload2 (IntegerMath2 math){
System.out.println("Integer2 " + math.apply(10, 20));
}
}
18

19. Разрешение неоднозначности

Если сигнатуры абстрактных методов целевого типа
отличаются только типами входных параметров, нужно:
либо указать типы входных параметров
либо использовать приведение к нужному целевому типу
TargetTypingExample obj = new TargetTypingExample();
// obj.overload((a, b) -> (a + b)); //ошибка компиляции
obj.overload((IntegerMath)(a, b) -> (a + b)); //overload (IntegerMath math)
obj.overload((int a, int b) -> (a + b)); //overload (IntegerMath math)
obj.overload((double a, double b) -> (a - b)); //overload (FloatMath math)
Если сигнатуры абстрактных методов целевого типа не
отличаются, поможет только явное приведение
obj.overload2((IntegerMath)(a, b) -> (a + b)); //overload(IntegerMath math)
obj.overload2((IntegerMath2)(a, b) -> (a + b)); //overload(IntegerMath2 math)
19

20. Доступ к внешним переменным

Лямбда-выражения не создают собственную область
видимости, и, соответственно, не создают проблем с
сокрытием переменных.
Однако имена фактических параметров лямбдавыражений могут совпадать с именами полей класса и,
соответственно, скрывать их.
Аналогично внутренним классам, лямбда-выражения
могут получать доступ к явным (объявленным как final),
так и фактически константным переменным (которые не
меняют своего значения после объявления).
20

21. Доступ к внешним переменным

public class FirstLevel {
public interface Consumer<T> {
public static int sx = 101;
static int sx = 1;
public int x = 102;
void accept(T t);
private int px = 103;
}
SecondLevel sl = new SecondLevel();
{
sl.x=303;
}
class SupLevel { protected int ssx = 401; }
class SecondLevel extends SupLevel {
public int sx = 201, x = 202;
private int px = 203;
void secondLevelMethod(int x){
int z =3;
//z+=2; // переменная не будет фактически константой
//x=99; // параметр не будет фактически константой
Consumer<Integer> myConsumer = (sx) -> {
System.out.println("x=" + x);
x=100
System.out.println("z=" + z);
z=3
System.out.println("this.x=" + this.x); this.x=202
21

22. Доступ к внешним переменным

System.out.println("FL.this.x=" + FirstLevel.this.x); FL.this.x=102
System.out.println("px=" + px);
px=203
System.out.println("this.px=" + this.px);
this.px=203
System.out.println("FL.this.px=" + FirstLevel.this.px); FL.this.px=103
System.out.println("sx=" + sx);
sx=100
System.out.println("this.sx=" + this.sx);
this.sx=201
System.out.println("FL.sx=" + FirstLevel.sx);
FL.sx=101
System.out.println("sl.x="+sl.x);
sl.x=303
System.out.println("ssx=" + ssx);
ssx=401
}
}
}
};
myConsumer.accept(x);
public static void main(String... args) {
FirstLevel fl = new FirstLevel();
FirstLevel.SecondLevel flsl = fl.new SecondLevel();
flsl.secondLevelMethod(100);
}
22

23. Сериализация

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

24. Пример сериализации

interface Function<T,R> extends Serializable {
R apply (T t);
}
class TestClass implements Serializable {
public int x = 2;
public TestClass(int x) { this.x = x; }
public boolean meth() {
return x < 10;
}
}
//сериализация, десериализация
ObjectOutputStream out = new ObjectOutputStream(…);
TestClass var = new TestClass(1);
out.writeObject((Function<Integer, String>)(value) ->
(value.equals(var.x)) ? "True" : "False");
out.close();
ObjectInputStream in = new ObjectInputStream(…);
Function<Integer, String> function =
(Function<Integer, String>) in.readObject();
24

25. Ссылки на методы

Если тело лямбда-выражения - вызов одного метода или
конструктора, можно использовать сокращенную форму
записи:
Ссылка на статический метод класса
ИмяКласса::имяСтатическогоМетода
Ссылка на не статический метод отдельного объекта
имяОбъекта::имяМетода
Ссылка на не статический метод произвольного объекта
определенного типа
ИмяТипа::имяМетода
Ссылка на конструктор
ИмяКласса::new
Параметры, передаваемые методу, компилятор
определит из контекста, в соответствии с целевым типом
25

26. Пример

class Man {
private String name;
private int age;
private LocalDate birthDate;
public Man(LocalDate birthDate, String name) { //…
}
public static int compareByAge(Man m1, Man m2) { return m2.age-m1.age;}
public int compareByBirthDate(Man m1, Man m2) {
return m1.birthDate.compareTo(m2.birthDate);
}
public int compareByName(Man m) { return this.name.compareTo(n.name); }
}
public String toString() { //…}
interface MansFactory {
Man get(LocalDate birthDate, String name);
}
26

27. Пример

Ссылка на конструктор класса Man
MansFactory mansFactory = Man::new; // вызов конструктора
Man[] mans = new Man[]{
mansFactory.get(LocalDate.now().minusYears(1), "Миша"),
mansFactory.get(LocalDate.now().plusWeeks(7), "Оля"),
mansFactory.get(LocalDate.now().minusMonths(6), "Андрей")
};
System.out.println(Arrays.deepToString(mans));
27

28. Пример

Ссылка на статичный метод compareByAge() класса Man
//Обычная лямбда
Comparator<Man> mansComparator = (m1, m2) -> Man.compareByAge(m1, m2);
//То же самое, но с использованием method reference
mansComparator = Man::compareByAge;
//Arrays.sort(mans, Man::compareByAge);
Ссылка на не статичный метод compareByBirthDate()
экземпляра класса Man
final Man m = new Man();
//Обычная лямбда
Comparator<Man> mansComparator = (m1,m2) ->
m.compareByBirthDate(m1,m2);
//То же самое, но с использованием method reference
mansComparator = m::compareByBirthDate;
//Arrays.sort(mans, m::compareByBirthDate);
28

29. Пример

Ссылка на не статичный метод compareByName()
объектов класса Man
mansComparator = Man::compareByName;
Arrays.sort(mans, Man::compareByName);
Формально,
тип
второго
параметра
метода
Arrays.sort()

функциональный интерфейс
Comparator<T>, и его метод compareTo(T o1, To2)
ожидает 2 входных параметра.
При использовании такого вида ссылок на метод, в
данном случае будет вызов o1.compareByName(o2)
29

30. Пакет java.util.function

Содержит базовый набор функциональных интерфейсов –
целевых типов для лямбда выражений
Function<T,R>
R apply(T t))
BiFunction<T,U,R>
R apply(T t, U u)
Predicate<T>
Boolean test(T t))
Consumer<T>
void accept(T t))
Supplier<T>
T get())

30

31. Агрегатные операции остались за бортом

Потоки
Создание потоков
Сплитераторы
Фильтры
Коллекторы
Параллелелизм

31

32.

Спасибо за внимание!

33. Дополнительные источники

Нимейер, Патрик. Программирование на Java / Патрик Нимейер, Дэниэл Леук; [пер. с
англ. М.А. Райтмана]. – Москва : Эксмо, 2014. – 1216 с.
Шилдт, Г. Java 7- Полное руководство - 8th Edition. – М.: ООО «И.Д. Вильямс», 2012г.
– 1104 с.
Хорстманн, К. Java 2. Библиотека профессионала. Том 1. Основы [Текст] / Кей
Хорстманн, Гари Корнелл. – М. : Издательский дом «Вильямс», 2010 г. – 816 с.
Эккель, Б. Философия Java [Текст] / Брюс Эккель. – СПб. : Питер, 2011. – 640 с.
JavaSE at a Glance [Электронный ресурс]. – Режим доступа:
http://www.oracle.com/technetwork/java/javase/overview/index.html, дата доступа:
19.02.2016.
JavaSE APIs & Documentation [Электронный ресурс]. – Режим доступа:
http://docs.oracle.com/javase/8/, дата доступа: 19.02.2016.
Tutorial: Lambda expressions [Электронный ресурс]. – Режим доступа:
https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html, дата доступа:
19.02.2016.
English     Русский Rules