Функциональные интерфейсы
Как мы уже обсуждали, в Java есть много существующих интерфейсов, которые инкапсулируют блоки кода, такие, как Runnable или Comparator. Лямбда-выражения имеют обратную совместимость с этими интерфейсами.
Вы можете поставить лямбда-выражение всякий раз, когда ожидается объект интерфейса с одним абстрактным методом. Такой интерфейс называется функциональным интерфейсом.
Вы можете удивиться, почему функциональный интерфейс должен иметь единственный абстрактный метод. Разве не все методы в интерфейсе абстрактные? На самом деле, всегда было возможно для интерфейса переопределить методы класса Object, например, toString или clone, и эти объявления не делают методы абстрактными. (Некоторые интерфейсы в Java API переопределяют методы Object, чтобы присоединить javadoc-комментарии. Посмотрите Comparator API для примера.) Что еще более важно, как вы вскоре увидите, в Java 8 интерфейсы могут объявлять неабстрактные методы.
Чтобы продемонстрировать преобразование в функциональный интерфейс, рассмотрим метод Arrays.sort. Его второй параметр требуется экземпляр Comparator, интерфейса с единственным методом. Просто предоставьте лямбду:
Arrays.sort(strs,
(firstStr, secondStr) -> Integer.compare(firstStr.length(), secondStr.length()));
За кулисами, метод Arrays.sort получает объект некоторого класса, реализующего Comparator . Вызов метода compare на этом объекте выполняет тело лямбда-выражения. Управление этими объектами и классами полностью зависит от реализации, и это может быть что-то гораздо более эффективное, чем использование традиционных внутренних классов. Лучше всего думать о лямбда-выражении как о функции, а не об объекте, и признать, что он может быть передан функциональному интерфейсу.
Это преобразование в интерфейсы – это то, что делает лямбда-выражения настолько мощными. Синтаксис короткий и простой. Вот еще один пример:
button.setOnAction(event ->
System.out.println("The button has been clicked!"));
Этот код очень легко читать. В самом деле, преобразование в функциональный интерфейс - это единственное, что вы можете сделать с лямбда-выражением в Java. В других языках программирования, которые поддерживают функциональные литералы, можно объявить типы функций, таких как (String, String) -> int, объявлять переменные этих типов, и использовать переменные для сохранения функциональных выражений. В Java вы не можете даже присвоить лямбда-выражение переменной типа Object, потому Object не является функциональным интерфейсом. Проектировщики Java решили строго придерживаться знакомой концепции интерфейсов, а не добавлять типы функций в язык.
Java API определяет несколько универсальных функциональных интерфейсов в пакете java.util.function. Один из интерфейсов, BiFunction , описывает функции с типами Т и U и типом возвращаемого значения R. Вы можете сохранить вашу лямбду сравнения строк в переменной этого типа:
BiFunction compareFunc
= (firstStr, secondStr) -> Integer.compare(firstStr.length(), secondStr.length());
Тем не менее, это не поможет вам с сортировкой. Не существует метода Arrays.sort, который принимает BiFunction. Если вы использовали функциональный язык программирования и прежде, вы можете найти это любопытным. Но для Java программистов это довольно естественно. Такой интерфейс, как Comparator, имеет конкретную цель, а не просто метод с заданным параметром и возвращаемым типом. Java 8 сохраняет этот стиль. Если вы хотите сделать что-то с лямбда-выражениями , вы все еще должны понимать назначение этого выражения, и иметь конкретный функциональный интерфейс для этого.
Интерфейсы из java.util.function используются в нескольких Java 8 интерфейсах API , и вы, вероятно, увидите их в других местах в будущем. Но имейте в виду, что вы можете одинаково хорошо преобразовать лямбда-выражение в функциональный интерфейс, который является частью любого API, который вы используете сегодня. Кроме того, вы можете пометить любой функциональный интерфейс с помощью аннотации @FunctionalInterface. Это имеет два преимущества. Компилятор проверяет, что аннотированная сущность представляет собой интерфейс с одним абстрактным методом. И страница Javadoc включает в себя утверждение, что ваш интерфейс является функциональным интерфейсом. Вы не обязаны использовать аннотацию. Любой интерфейс с одним абстрактным методом является, по определению, функциональным интерфейсом. Но использование аннотации @FunctionalInterface - это хорошая идея.
Наконец, заметим, что checked исключения могут возникнуть при преобразовании лямбды в экземпляр функционального интерфейса. Если тело лямбда-выражения может бросить checked исключение, это исключение должно быть объявлено в абстрактном методе целевого интерфейса. Например, следующее было бы ошибкой:
Runnable sleepingRunner = () -> { System.out.println("…"); Thread.sleep(1000); };
// Error: Thread.sleep can throw a checkedInterruptedException
Поскольку Runnable.run не может бросить исключение, это присваивание является некорректным. Чтобы исправить ошибку, у вас есть два варианта. Вы можете поймать исключение в теле лямбда-выражения. Или вы можете присвоить лямбду интерфейсу, один абстрактный метод которого может бросить исключение. Например, метод call из интерфейса Callable может бросить любое исключение. Таким образом, вы можете присвоить лямбду Callable (если добавить return null).
Do'stlaringiz bilan baham: |