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


154

Я натрапив на новий термін у Java 8: "функціональний інтерфейс". Я міг знайти лише одне його використання під час роботи з лямбда-виразами .

Java 8 надає деякі вбудовані функціональні інтерфейси, і якщо ми хочемо визначити будь-який функціональний інтерфейс, ми можемо скористатись @FunctionalInterfaceанотацією. Це дозволить нам оголосити лише один метод в інтерфейсі.

Наприклад:

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

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

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


1
Здається, це дублікат цього посилання. Вони також розповідають про те, чому у функціональному інтерфейсі повинен бути лише один метод. stackoverflow.com/questions/33010594/…
Kulbhushan Singh

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"; Foo - це не функціональний інтерфейс


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 немає іншого випадку використання його.


Можна просто попросити трохи забагато або неактуально (ви можете вибрати, щоб не відповідати), але що б ви запропонували, коли хтось створив утиліту public static String generateTaskId()проти того, щоб зробити її більш "функціональною", хтось інший вирішив написати це як public class TaskIdSupplier implements Supplier<String>у getметоді, використовуючи реалізація існуючих поколінь. Це неправильне використання функціональних інтерфейсів, особливо повторне використання Supplierвбудованого JDK? PS: Я не зміг знайти кращого місця / Питання та відповіді. Раді перенести, якщо ви можете запропонувати.
Наман

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;
        }
    }

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 <>.

Застосування функціонального інтерфейсу:

  • Посилання на метод
  • Лямбда-вираз
  • Посилання на конструктор

Для вивчення функціональних інтерфейсів, вивчення перших методів за замовчуванням в інтерфейсі, а після вивчення функціонального інтерфейсу вам буде легко зрозуміти посилання методу та лямбда-вираз


Чи повинні ваші перші два приклади мати "абстрактне" ключове слово?
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


1

@FunctionalInterface це нова анотація, яка випускається з Java 8 і надає цільові типи для лямбда-виразів, і вона використовується для перевірки часу компіляції коду.

Коли ви хочете використовувати його:

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

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

java.util.functionПакет містить різні функціональні інтерфейси загального призначення , такі як Predicate, Consumer, Function, і Supplier.

Також зверніть увагу, що ви можете використовувати лямбдаси без цієї анотації.


1

Крім інших відповідей, я вважаю, що основна причина "чому використання функціонального інтерфейсу, окрім безпосередньо з лямбда-виразами", може бути пов'язана з природою мови Java, яка орієнтована на об'єкти.

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

Наприклад, у Java Script, функція (Anonymous function або Function literals) може бути адресована як об'єкт. Отже, ви можете їх створити просто, а також вони можуть бути призначені змінній тощо. Наприклад:

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

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

const myFunction = ... => ...

Дотепер дизайнери мови Java не прийняли обробляти згадані функції цим способом (функціональні методи програмування). Вони вважають, що мова Java орієнтована на об'єкт, тому вони повинні вирішити цю проблему за допомогою об'єктно-орієнтованих методик. Вони не хочуть пропускати простоту і послідовність мови Java.

Тому вони використовують інтерфейси, тому що коли об'єкт інтерфейсу лише одним методом (я маю на увазі функціональний інтерфейс) є необхідним, ви можете замінити його виразом лямбда. Як от:

ActionListener listener = event -> ...;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.