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

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

Java всегда была объектно-ориентированным языком программирования. А это означает, что все вращается вокруг объектов (за исключением некоторых примитивных типов). Любые методы и функции в Java являются частью класса, поэтому мы должны использовать класс/объект для вызова любой функции. Так что же в этот гармоничный мир привнесла Java 8?

Если мы посмотрим на такие языки программирования, как C++, JavaScript, то они официально называются функциональными языками. Это значит, что мы можем писать функции и использовать их при необходимости. Такие языки поддерживают объектно-ориентированное программирование, а также функциональное программирование.

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

Обратите внимание, нам нужно лишь то, что находится внутри метода run(). Остальной код является лишь описательной частью, особенностью написания кода на Java.

Что предлагает нам Java 8?

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

Функциональные интерфейсы

Интерфейс с одним абстрактным методом называется функциональным интерфейсом. Чтобы отметить интерфейс как функциональный, используется аннотация @FunctionalInterface. Она не является обязательной, но ее использование считается хорошим тоном программирования на Java 8. Также эта аннотация помогает избежать случайного добавления дополнительных методов.

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

Collections API в Java 8 был полностью переписан для добавления возможности использовать функциональные интерфейсы. На данный момент в Java 8 есть множество функциональных интерфейсов, которые находятся в пакете java.util.function. Наиболее полезными для нас являются интерфейсы Consumer, Supplier, Function и Predicate. Подробнее о них читайте в нашей статье Полное руководство по Java 8 Strem API.

Лучшим примером функционального интерфейса является интерфейс java.lang.Runnable с одним абстрактным методом run().

А теперь давайте рассмотрим некоторые рекомендации по использованию функциональных интерфейсов:

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

Лямбда-выражения способ визуализировать функциональное программирование в объектно-ориентированном Java-мире. Объекты являются основой языка программирования Java и мы не представляем себе функцию без объекта. Именно поэтому Java обеспечивает поддержку лямбда-выражений только с функциональными интерфейсами.

Мы уже знаем, что в функциональных интерфейсах есть только один метод, именно поэтому не возникает путаницы в применении лямбда-выражений. Синтаксис лямбда-выражения представляет собой такую структуру (аргумент) -> (содержимое лямбда-выражения). Теперь давайте перепишем приведенный выше Runnable с помощью лямбда-выражения:

Давайте разберемся, что же происходит в лямбда-выражении выше.

  • Runnable — это функциональный интерфейс, поэтому мы можем использовать лямбда-выражения для создания его экземпляра.
  • Метод run() не принимает аргументов, поэтому наша лямбда также не содержит аргумент.
  • Мы не используем фигурные скобки ({}), потому что у нас только одно выражение в теле метода. В других случаях мы должны использовать фигурные скобки, как и в любом другом методе.

Почему мы должны использовать лямбда-выражения?

  1. Меньше строк кода. Одной из очевидных преимуществ лямбда-выражений является уменьшение объема кода, потому что мы можем создать экземпляр интерфейса с помощью функционального лямбда-выражения, а не с помощью анонимного класса.
  2. Поддержка последовательного и параллельного выполнения. Еще одно преимущество использования лямбда-выражения в том, что мы можем использовать Stream API для выполнения последовательных или параллельных операций.Чтобы объяснить это, давайте возьмем простой пример, где мы должны написать метод проверки числа на простоту.В Java 7 и ниже мы бы написали код так, как показано ниже. Код не очень оптимизирован, но хорошо подходит для примера:
Проблема приведенного выше кода в том, что это последовательная обработка и если число операций будет очень большим, то это займет значительное время. Еще одна проблема в том, что в коде много точек выхода из метода, следовательно падает читабельность кода.

А теперь давайте посмотрим приведенный выше метод с использованием лямбда-выражения и Stream API:

Из примера видно, что кода стало меньше, а вот читабельность не очень увеличилась, правда?

Маленькое пояснение к коду: IntStream — последовательность примитивных int-овых элементов, поддерживающих последовательные и параллельные операции. Это примитивная int-специализация для Stream.

Давайте же улучшим читаемость кода и немного перепишем наш код:

Использование лямбда-выражений. Пример №1

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

И пример использования этого метода:

Лямбда-выражения. Пример 2

В следующем примере мы рассмотрим повышение эффективности вычислений с помощью «ленивых вычислений». Возьмем задание из какой-то лабораторной по курсу Java программирования, например, нам нужно написать метод вычисления максимального нечетного числа в диапазоне от 5 до 13 и вернуться его квадрат:

Обычно (в Java 7 и ниже) мы бы написали что-то похожее на это:

Приведенная выше программа всегда будет работать последовательно, но мы можем использовать Stream API используя ленивые вычисления. Давайте перепишем код выше в функциональный стиль со Stream API и лямбдами.

В нашем коде появился оператор двойного двоеточия (::), который также введен в Java 8 и используется для ссылок на методы. Java компилятор заботится о маппинге аргументов вызываемого метода.

То есть по сути это краткая форма лямбда-выражений i -> isGreaterThan5(i) или i -> NumberTest.isGreaterThan5(i).

Методы в Java 8

Ссылка на метод используется для того, чтобы обратиться к методу без его выполнения. Ссылка на Конструктор аналогично используется не создавая новый экземпляр класса.

В этой статья я попытался дать ту минимальную базу знаний для понимания функциональных интерфейсов и лямбда-выражений. Если вас заинтересовала Java 8, то почитайте о ней в отдельной статье «Новые возможности Java 8«, а также подробное руководство по Stream API. Также есть отдельная статья о Статических методах и методах по умолчанию в Java 8.

Следите за обновлениями раздела Java 8.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *