From 413cfb86d9ab2e7f64d2354b6f4f40f0bf3dc078 Mon Sep 17 00:00:00 2001 From: egorzhurov Date: Mon, 13 May 2024 22:54:44 +0300 Subject: [PATCH] Move 46 article to markdown format --- ...ional interfaces and lambda expressions.md | 264 ++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 lessons/java-core/046/Functional interfaces and lambda expressions.md diff --git a/lessons/java-core/046/Functional interfaces and lambda expressions.md b/lessons/java-core/046/Functional interfaces and lambda expressions.md new file mode 100644 index 0000000..b9fc17f --- /dev/null +++ b/lessons/java-core/046/Functional interfaces and lambda expressions.md @@ -0,0 +1,264 @@ +# Функциональные интерфейсы и лямбда-выражения + +В рамках предыдущего раздела – коллекций – мы научились хранить массивы данных так, как это принято в Java. Следующий +шаг – научиться обрабатывать массивы данных так, как это принято в современной (начиная с Java 8) Java-разработке. В +рамках данного раздела мы познакомимся с механизмами, которые реализуют **функциональное программирование (ФП)**. + +Но это будет в следующих уроках. Сегодня же мы познакомимся с тем, что к ФП напрямую не относится, но без чего ФП в Java +никогда не стало бы популярным – познакомимся с **лямбда-выражениями**. По сути, **синтаксическим сахаром** – +новым синтаксисом, который делает использование ранее существовавших механизмов более удобным. + +Данный урок предлагаю построить на разборе следующего определения: + +> **Лямбда-выражение** – упрощенная форма описания анонимного класса, реализующего **функциональный интерфейс**. + +Сразу оговорюсь, что это определение можно считать верным лишь непосредственно в Java. За пределами Java-мира оно будет +выглядеть как минимум странно. Зато оно дает возможность построить удобную структуру для текущего урока. + +## Функциональные интерфейсы + +Итак. Формулировка выше говорит об анонимном классе +(с ними мы уже [знакомы](https://telegra.ph/Vlozhennye-klassy-12-08)), реализующем функциональный интерфейс. + +**Функциональный интерфейс** – интерфейс, имеющий лишь один абстрактный метод. + +При этом такой интерфейс может содержать любое количество статических, приватных и default- методов. + +Также функциональные интерфейсы, как правило, помечаются аннотацией `@FunctionalInterface` (указывается над самим +интерфейсом). Она не является обязательной, но бросит ошибку компиляции, если помеченный ей интерфейс будет содержать +более одного абстрактного метода (или не будет содержать их вовсе). + +Как минимум с одним функциональным интерфейсом мы уже знакомы – это интерфейс `Comparator`. К слову, именно на нем будет +построена часть сегодняшней практики. + +В целом, функциональных интерфейсов достаточно много, но наиболее популярные мы рассмотрим в рамках статьи на metanit, +ссылка будет в следующем пункте данного урока. + +> !NB: Как правило, функциональные интерфейсы представляют собой дженерики. Но это вовсе не обязательно. Скорее, дело в +> том, что для большинства задач так удобнее. + +## Синтаксис лямбда-выражений + +Сначала предлагаю разобрать, как выглядит «описание анонимного класса, реализующего функциональный интерфейс». + +Для удобства предлагаю использовать уже знакомый нам интерфейс `Comparator`: + +```java + Comparator numberComparator = new Comparator<>() { + @Override + public int compare(Car o1, Car o2) { + return o1.getIdentifier() + .getNumber() + .compareTo(o2.getIdentifier().getNumber()); + } + }; +``` + +Так выглядело создание объекта анонимного класса, реализующего `Comparator`, в одной из +[задач урока 38](https://github.com/KFalcon2022/practical-tasks/tree/master/src/com/walking/lesson38_comparing/task1). + +Если попытаться перевести код выше на русский язык, мы получим примерно следующее: + +Переменная `numberComparator` является объектом анонимного класса, реализующего интерфейс `Comparator`, +параметризованного классом `Car`. Анонимный класс переопределяет метод `compare()`, принимающий два параметра одного +типа. Метод содержит следующую логику: _[описание логики метода]_. + +Наиболее интересующие нас моменты подчеркнуты. Учитывая, что интерфейс `Comparator` имеет лишь один абстрактный метод – +название этого метода, в целом, можно опустить для лаконичности. + +Используя лямбда-выражение, код выше можно описать следующим образом: + +```java + Comparator numberComparator = (o1, o2) -> + o1.getIdentifier() + .getNumber() + .compareTo(o2.getIdentifier().getNumber()); +``` + +Разберемся, что есть что: + +`Comparator numberComparator` – эта часть осталась без изменений. Переменная `numberComparator`, тип ссылки - +интерфейс `Comparator`, параметризованный классом `Car`. Не является частью лямбда-выражения, это просто объявление +переменной. Здесь оставлено для удобства. + +`(o1, o2)` – два параметра одного типа. Типы параметров в лямбда-выражении можно указать явно, но их принято опускать – +все равно они будут определены на основании сигнатуры метода, переопределением которого является лямбда-выражение. + +> Содержи сигнатура метода два параметра разных типов – форма записи осталась бы такой же. + +> Будь в исходном методе 10 параметров – в скобках перечислили бы 10 параметров, ни будь ни одного – +> скобки были бы пусты: `()`. + +> Если бы параметр был один – скобки можно было бы опустить: `o -> …` + +`->` – оператор лямбда-выражения. Слева от него располагаются параметры выражения (в нашем случае – `(o1, o2)`), +после него – тело лямбда-выражения; + +`o1.getIdentifier()…` – все то, что после `«->»`. Олицетворяет собой _[описание логики метода]_. + +> Если тело метода было представлено одной строчкой кода (одна инструкция, завершающаяся `«;»`) – +> при ее представлении в виде лямбда-выражения можно опустить фигурные скобки `{}` и ключевое слово `return`. + +Также рассмотрим то же самое лямбда-выражение, представленное в несколько инструкций (строк кода). +Смысл эквивалентный, только логика описана с использованием дополнительных переменных: + +```java + Comparator numberComparator = (o1, o2) -> { + String number1 = o1.getIdentifier().getNumber(); + String number2 = o2.getIdentifier().getNumber(); + + return number1.compareTo(number2); + }; +``` + +Как видите, мы все еще имеем дело с лямбда-выражением, но его немного «разнесло». Многострочные лямбда-выражения – +допустимая, но нежелательная практика. Однако на данном этапе для нас не критично. + +Внутри лямбда-выражений, как и в случае с анонимными классами, можно использовать переменные/поля/методы, определенные +за их пределами. Но с теми же ограничениями: значения ссылок полей и переменных изменять нельзя. + +Теперь предлагаю посмотреть, насколько сократился наш код при использовании лямбда-выражения: + +```java + Comparator numberComparator = new Comparator<>() { + @Override + public int compare(Car o1, Car o2) { + return o1.getIdentifier() + .getNumber() + .compareTo(o2.getIdentifier().getNumber()); + } + }; + cars.sort(numberComparator); +``` + +Превратилось в: + +```java + Comparator numberComparator = (o1, o2) -> o1.getIdentifier() + .getNumber() + .compareTo(o2.getIdentifier().getNumber()); + + cars.sort(numberComparator); +``` + +Учитывая, что лямбда-выражения редко записывают в переменные и определяют по месту вызова – можно сократить до: + +```java + cars.sort((o1, o2) -> o1.getIdentifier() + .getNumber() + .compareTo(o2.getIdentifier().getNumber())); +``` + +> !NB: конкретно в случае с `Comparator` подобную логику можно (и нужно) свернуть до: +> `cars.sort(Comparator.comparing(o -> o.getIdentifier().getNumber()));` +> Но это не относится к теме урока. + +Предлагаю рассмотреть примеры определения лямбда-выражений для различных функциональных интерфейсов в рамках +[следующей статьи](https://metanit.com/java/tutorial/9.3.php) + +## Об отложенном выполнении и не только + +Для начала, предлагаю ознакомиться с данной [статьей](https://metanit.com/java/tutorial/9.1.php) + +По ряду причин я не считаю ее удачной в плане структуры, в остальном она перескажет содержание того, что мы изучили выше +более подробно. + +В данном пункте я предлагаю остановиться на том, что на метаните описано под пунктом «Отложенное выполнение». Именно с +этим возникает большинство проблем, с которыми сталкиваются новички. + +Также, как и с описанием анонимных классов, стоит помнить, что код переопределяемых методов – это именно инструкции, +которые описывают поведение для объектов классов. Код, который вы пишете в методе (методах) анонимного класса, не +вызовется сразу. Он будет исполнен тогда (и только тогда), когда вы вызовете у объекта этого класса конкретный метод. А +лямбда выражение – это всего лишь объект анонимного класса с единственным абстрактным методом. + +Обобщая, помните, что анонимный класс – это тоже класс. Но ни в коем случае не часть инструкций (кода) метода (или +класса), в котором этот анонимный класс описан. + +И точно также, как вы не ожидаете немедленного вызова всех методов класса `String`, создавая экземпляр строки, а лишь +хотите видеть их запуск при прямом вызове, так и при описании анонимных классов стоит ожидать запуск его методов лишь +при прямом вызове у объекта. Даже если ваш анонимный класс описан как лямбда-выражение. + +Глядя на практическое применение лямбда-выражений, вы заметите, что 99% использования – это передача лямбда-выражения +параметром метода. На самом деле происходит передача объекта анонимного класса, описанного как лямбда-выражение. И +внутри метода обязательно будет произведен вызов метода, который вы переопределили в своем анонимном +классе/лямбда-выражении. + +Скажем, `list.sort()` внутри себя обязательно вызовет `comparator.compare()`. И именно при каждом таком вызове будет +запускаться код, который вы описали в лямбда-выражении. При этом между описанием лямбда-выражения и использующем +его `list.sort()` могут быть сотни строк другого кода. + +## Немного о декомпозиции и написании кода + +Лямбда-выражения являются очень важным инструментом в современной Java-разработке. Но, к сожалению, большинство +разработчиков используют лишь малую долю потенциала лямбд, даже не представляя, как использовать их более эффективно. + +Ссылка ниже, надеюсь, станет для вас первым шагом к гибкому использованию лямбда-выражений. + +Помните, что лямбда-выражение – это всего лишь объект анонимного класса, а значит, может быть присвоен полю, переменной, +быть параметром или возвращаемым значением метода (вашего метода, а не написанного разработчиками языка или библиотеки). + +Также по ссылке ниже вы найдете еще одну новую синтаксическую конструкцию – **method reference – ссылка на метод**. +Это следующих шаг оптимизации кода, делающий который укорачивает уже описание лямбда-выражения. +Этот механизм может быть применен не к любой лямбде и имеет ряд особенностей. Несмотря на кажущуюся простоту +использования, потребуются некоторые усилия, чтобы понять, как им пользоваться. + +Но он, безусловно, полезен, поэтому данному механизму будет целиком посвящен один из ближайших уроков. +Надеюсь, это добавит осознанности при использовании _method reference_ в вашем коде*. + +> *Личный опыт автора говорит о том, что ощутимая доля Java-разработчиков использует _method reference_ лишь тогда, +> когда IDEA предлагает сформировать его автоматически на основании написанного лямбда-выражения. + +Собственно, обещанная [ссылка](https://metanit.com/java/tutorial/9.2.php) + +## Вместо итога + +Сегодняшний урок почти полностью посвящен именно синтаксису. Поэтому я предполагаю, что для многих останется непонятной +широта области применения лямбда-выражений. И это нормально. Нас впереди ждет Stream API, знакомство с `Optional` и +методы коллекций, взаимодействующие с лямбдами (последние начнутся уже сегодня). Именно указанные темы призваны +наполнить механизм лямбда-выражений смыслом. И именно они позволят раскрыть удобство использования лямбда-выражений в +полной мере. + +#### С теорией на сегодня все! + +![img.png](../../../commonmedia/defaultFooter.jpg) + +Переходим к практике: + +## Задача 1: + +Реализуйте +[Задачу 1 из урока 38](https://github.com/KFalcon2022/practical-tasks/tree/master/src/com/walking/lesson38_comparing/task1), +описывая компараторы как лямбда-выражения. + +## Задача 2: + +Знакомимся с функциональным интерфейсом `Consumer`. Используя реализацию Задачи 3 из урока 16 по +[ссылке](https://github.com/KFalcon2022/practical-tasks/tree/master/src/com/walking/lesson16_abstract_class_interface/task3), +замените массив на список, а цикл `for` – на вызов метода `forEach()`, который доступен для всех наследников `Iterable`. +Он теперь будет вашим другом и надежным соратником:) + +## Задача 3 (*): + +Реализуйте +[задачу из урока 21](https://github.com/KFalcon2022/practical-tasks/tree/master/src/com/walking/lesson21_immutable_object), +с использованием списка (или другой коллекции на ваш выбор). Дайте возможность искать машины по гибкому фильтру – +возвращайте коллекцию машин, подходящих под конкретный фильтр (можете расширить на свой вкус): + +- Номер совпадает с введенным пользователем; +- Номер содержит подстроку, указанную пользователем; +- Цвет совпадает с указанным пользователем; +- Год выпуска машины находится в диапазоне, указанном пользователем. + +При этом `CarService` должен содержать лишь один публичный метод поиска. Можете использовать `Predicate` или собственный +функциональный интерфейс. + +Также реализуйте интерактивное меню в рамках консоли, позволяющее производить несколько поисков в рамках одного запуска +программы. Предусмотрите возможность завершения программы с помощью пользовательского ввода. + +> Если что-то непонятно или не получается – welcome в комменты к посту или в лс:) +> +> Канал: https://t.me/ViamSupervadetVadens +> +> Мой тг: https://t.me/ironicMotherfucker +> +> **Дорогу осилит идущий!** \ No newline at end of file