Для чего используются функциональные интерфейсы в Java 8?

154

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

Java 8 предоставляет несколько встроенных функциональных интерфейсов, и если мы хотим определить какой-либо функциональный интерфейс, мы можем использовать @FunctionalInterfaceаннотацию. Это позволит нам объявить только один метод в интерфейсе.

Например:

@FunctionalInterface
interface MathOperation {
    int operation(int a, int b);
}

Насколько это полезно в Java 8, кроме работы с лямбда-выражениями ?

(Вопрос здесь отличается от того, который я задавал. Он спрашивает, зачем нам нужны функциональные интерфейсы при работе с лямбда-выражениями. Мой вопрос: почему другие применения используются функциональными интерфейсами помимо с лямбда-выражениями?)

Мадхусудан
источник
1
Похоже, дублировать эту ссылку. Они также говорят о том, почему в функциональном интерфейсе должен быть только один метод. stackoverflow.com/questions/33010594/…
Кулбхушан Сингх,
1
@KulbhushanSingh Я видел этот вопрос перед публикацией ... Оба вопроса чувствуют разницу ...
Madhusudan

Ответы:

127

@FunctionalInterfaceаннотация полезна для проверки времени компиляции вашего кода. Вы не можете иметь более одного метода , кроме static, defaultи абстрактные методы , которые переопределяют методы в Objectв вашей @FunctionalInterfaceили любой другой интерфейс , используемый в качестве функционального интерфейса.

Но вы можете использовать лямбда-выражения без этой аннотации, а также переопределять методы без @Overrideаннотации.

Из документов

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

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

public interface Foo {
  public void doSomething();
}

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

public interface Foo {
  public void doSomething();
  public void doSomethingElse();
}

Но это даст ошибку компиляции :

@FunctionalInterface
public interface Foo {
  public void doSomething();
  public void doSomethingElse();
}

Неверная аннотация @FunctionalInterface; Фу не функциональный интерфейс

Сергей Бишыр
источник
43
Точнее, у вас должен быть ровно один абстрактный метод, который не переопределяет метод в java.lang.Objectфункциональном интерфейсе.
Хольгер
9
... и это немного отличается от "не иметь более одного publicметода, кроме как staticи default" ...
Хольгер,
4
Все еще не понимаю смысла иметь это. Зачем кому-то на земле беспокоиться о том, сколько методов использует его / ее интерфейс? Интерфейсы маркеров по-прежнему имеют смысл и конкретную цель. Документация и ответ только объясняют, что она делает, а не как она вообще нужна. И «использование» - это именно то, о чем спрашивал ОП. Поэтому я бы не рекомендовал этот ответ.
saran3h
1
@ VNT ошибки компиляции получают клиенты этого интерфейса, но не сам интерфейс может измениться. С этой аннотацией ошибка интерфейса находится на интерфейсе, так что вы убедитесь, что никто не сломает клиентов вашего интерфейса.
Сергей Бишыр,
2
Это показывает, как их использовать, но не объясняет, почему они нам нужны.
Шейх
14

Документация делает на самом деле разница между целью

Информативный тип аннотации, используемый, чтобы указать, что объявление типа интерфейса предназначено, чтобы быть функциональным интерфейсом, как определено Спецификацией языка Java.

и вариант использования

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

чья формулировка не исключает других случаев использования в целом. Поскольку основной целью является указание на функциональный интерфейс , ваш фактический вопрос сводится к «Есть ли другие варианты использования для функциональных интерфейсов, кроме лямбда-выражений и ссылок на метод / конструктор?»

поскольку функциональным интерфейсом является языковой конструкцией Java, определенной в Спецификации языка Java, только эта спецификация может ответить на этот вопрос:

JLS §9.8. Функциональные интерфейсы :

...

В дополнение к обычному процессу создания экземпляра интерфейса путем объявления и создания экземпляра класса (§15.9), экземпляры функциональных интерфейсов могут быть созданы с помощью выражений ссылок на методы и лямбда-выражений (§15.13, §15.27).

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

Так что в одном предложении нет, в Java 8 нет другого варианта использования.

Holger
источник
Можно просто попросить немного слишком много или не относиться к делу (вы можете не отвечать), но что бы вы предложили, когда кто-то создал утилиту, public static String generateTaskId()а не для того, чтобы сделать ее более «функциональной», кто-то другой решил написать ее так же, как public class TaskIdSupplier implements Supplier<String>при использовании getметода, использующего реализация существующего поколения. Это неправильное использование функциональных интерфейсов, особенно повторное использование Supplierвстроенного JDK? PS: я не мог найти лучшее место / Q & A, чтобы спросить это. Рад мигрировать, если вы могли бы предложить.
Наман
1
@Naman, вы не делаете вспомогательный метод более функциональным, когда создаете именованный класс TaskIdSupplier. Теперь вопрос в том, почему вы создали названный класс. Существуют сценарии, где требуется такой именованный тип, например, когда вы хотите поддержать поиск реализации через ServiceLoader. Нет ничего плохого в том, чтобы позволить ему реализовать это Supplier. Но когда вам это не нужно, не создавайте его. Когда вам нужно только a Supplier<String>, уже достаточно использовать DeclaringClass::generateTaskIdи исключить необходимость явного класса - вот смысл этой языковой функции.
Хольгер
Честно говоря, я искал оправдание для рекомендации, которую я передавал. По какой-то причине на работе я на самом деле не чувствовал, что TaskIdSupplierреализация стоила затраченных усилий, но тогда концепция ServiceLoaderполностью скинула мне с ума. Встречаются несколько вопросов в ходе этих дискуссий , которые мы имели , такие как Что такое использование Supplier«S publicсуществования , когда можно идти вперед и развивать свои собственные интерфейсы? и почему бы не иметь public static Supplier<String> TASK_ID_SUPPLIER = () ->...в качестве глобальной константы? , (1/2)
Наман
1
@Naman идиоматический способ представления функций в Java - это методы, и оценка этих функций идентична их вызову. Никогда не следует заставлять разработчика делать variable.genericMethodName(args)вместо meaningfulMethodName(args). Использование типа класса для представления функции, будь то с помощью лямбда-выражения / ссылки на метод или вручную созданного класса, является лишь средством передачи функции (при отсутствии истинных типов функций в Java). Это должно быть сделано только при необходимости.
Хольгер
1
Когда у вас есть небольшой фрагмент кода, который только передается, вы можете создать лямбда-выражение, инкапсулирующее его. Всякий раз, когда необходимо также вызвать его как метод (это включает в себя сценарии с необходимостью тестирования, когда фрагмент кода не является тривиальным), создайте именованный метод, который можно вызывать, и используйте ссылку на метод или лямбда-выражение / явный класс инкапсуляция вызова, чтобы передать его при необходимости. Константы полезны, только когда вы не доверяете эффективности лямбда-выражений или ссылок на методы, встроенных в ваш код, другими словами, они почти никогда не нужны.
Хольгер
12

Как уже говорили другие, функциональный интерфейс - это интерфейс, который предоставляет один метод. Может иметь более одного метода, но все остальные должны иметь реализацию по умолчанию. Причина, по которой он называется «функциональным интерфейсом», заключается в том, что он эффективно действует как функция. Поскольку вы можете передавать интерфейсы как параметры, это означает, что функции теперь являются «первоклассными гражданами», как в функциональных языках программирования. Это имеет много преимуществ, и вы увидите их довольно часто при использовании Stream API. Конечно, лямбда-выражения являются основным очевидным использованием для них.

Сина Мадани
источник
10

Не за что. Лямбда-выражения - это единственная точка этой аннотации.

Луи Вассерман
источник
6
Хорошо, lamdbas работает без аннотации также. Это утверждение так же, как @Overrideесли бы вы сообщили компилятору, что вы намеревались написать что-то «функциональное» (и получите ошибку, если вы поскользнулись)
Тило
1
Прямо в точку и правильный ответ, хотя и немного короткий. Я не торопился, чтобы добавить более подробный ответ, говорящий то же самое с большим количеством слов…
Хольгер,
5

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

Хорошая особенность конкретных функциональных интерфейсов в java.util.functionтом, что они могут быть созданы для создания новых функций (например, Function.andThenи Function.compose, Predicate.andи т. Д.) Благодаря удобным методам по умолчанию, которые они содержат.

Хэнк Д
источник
Вы должны уточнить этот комментарий больше. Как насчет ссылок на методы и новых функций?
К.Николас
5

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

package com.akhi;
    @FunctionalInterface
    public interface FucnctionalDemo {

      void letsDoSomething();
      //void letsGo();      //invalid because another abstract method does not allow
      public String toString();    // valid because toString from Object 
      public boolean equals(Object o); //valid

      public static int sum(int a,int b)   // valid because method static
        {   
            return a+b;
        }
        public default int sub(int a,int b)   //valid because method default
        {
            return a-b;
        }
    }
Akhilesh
источник
3

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

  • Введено в Java 8
  • Интерфейс, который содержит «единственный абстрактный» метод.

Пример 1:

   interface CalcArea {   // --functional interface
        double calcArea(double rad);
    }           

Пример 2:

interface CalcGeometry { // --functional interface
    double calcArea(double rad);
    default double calcPeri(double rad) {
        return 0.0;
    }
}       

Пример 3:

interface CalcGeometry {  // -- not functional interface
    double calcArea(double rad);
    double calcPeri(double rad);
}   

Java8 аннотация - @FunctionalInterface

  • Проверка аннотации, что интерфейс содержит только один абстрактный метод. Если нет, поднять ошибку.
  • Даже если @FunctionalInterface отсутствует, это все же функциональный интерфейс (если используется один абстрактный метод). Аннотация помогает избежать ошибок.
  • Функциональный интерфейс может иметь дополнительные статические методы и методы по умолчанию.
  • например, Iterable <>, Comparable <>, Comparator <>.

Приложения функционального интерфейса:

  • Ссылки на метод
  • Лямбда-выражение
  • Ссылки на конструктор

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

Ketan
источник
Должны ли ваши первые два примера иметь ключевое слово 'abstract'?
sofs1
1
@ sofs1 Методы, объявленные в интерфейсах, по умолчанию являются общими и абстрактными. Вы должны использовать абстрактное ключевое слово в случае методов в абстрактном классе. Однако можно использовать абстрактное ключевое слово для методов в интерфейсе. Они разрешили это для совместимости более старой версии Java, но это не рекомендуется.
Кетан
2

Вы можете использовать лямбду в Java 8

public static void main(String[] args) {
    tentimes(inputPrm - > System.out.println(inputPrm));
    //tentimes(System.out::println);  // You can also replace lambda with static method reference
}

public static void tentimes(Consumer myFunction) {
    for (int i = 0; i < 10; i++)
        myFunction.accept("hello");
}

Для получения дополнительной информации о Java Lambdas и FunctionalInterfaces

Websterix
источник
1

@FunctionalInterface Это новая аннотация, выпущенная с Java 8 и предоставляющая целевые типы для лямбда-выражений, и она используется при проверке времени компиляции вашего кода.

Когда вы хотите использовать это:

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

1- Ваш интерфейс должен быть чистым, что означает, что функциональный интерфейс предназначен для реализации классами без сохранения состояния, примером чистого является Comparatorинтерфейс, потому что он не зависит от состояния разработчиков, в данном случае Нет ошибку компиляции будет дана, но во многих случаях не сможет использовать лямбда с такими интерфейсами

java.util.functionПакет содержит различные функциональные интерфейсы общего назначения , такие как Predicate, Consumer, Function, иSupplier .

Также обратите внимание, что вы можете использовать лямбды без этой аннотации.

Ахмад аль-Курди
источник
1

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

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

Например, в Java Script, функция (анонимная функция или литералы функций) может быть адресована как объект. Таким образом, вы можете создавать их просто, а также они могут быть назначены на переменную и так далее. Например:

var myFunction = function (...) {
    ...;
}
alert(myFunction(...));

или через ES6, вы можете использовать функцию стрелки.

const myFunction = ... => ...

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

Поэтому они используют интерфейсы, так как, когда нужен объект интерфейса только с одним методом (я имею в виду функциональный интерфейс), вы можете заменить его лямбда-выражением. Такие как:

ActionListener listener = event -> ...;
MMKarami
источник