Do czego służą interfejsy funkcjonalne w Javie 8?


154

W Javie 8 natknąłem się na nowy termin: „interfejs funkcjonalny”. Podczas pracy z wyrażeniami lambda mogłem znaleźć tylko jedno jego użycie .

Java 8 zapewnia wbudowane interfejsy funkcjonalne i jeśli chcemy zdefiniować dowolny interfejs funkcjonalny, możemy skorzystać z @FunctionalInterfaceadnotacji. Pozwoli nam to zadeklarować tylko jedną metodę w interfejsie.

Na przykład:

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

Jak przydatne jest to w Javie 8 poza samą pracą z wyrażeniami lambda ?

(Pytanie tutaj różni się od tego, które zadałem. Chodzi o pytanie, dlaczego potrzebujemy interfejsów funkcjonalnych podczas pracy z wyrażeniami lambda. Moje pytanie brzmi: dlaczego interfejsy funkcjonalne mają inne zastosowania oprócz wyrażeń lambda?)


1
Wygląda na to, że ten link jest identyczny. Mówią również o tym, dlaczego w interfejsie funkcjonalnym powinna być tylko jedna metoda. stackoverflow.com/questions/33010594/…
Kulbhushan Singh

1
@KulbhushanSingh Widziałem to pytanie przed wysłaniem ... Oba pytania mają znaczenie ...
Madhusudan

Odpowiedzi:


127

@FunctionalInterfaceadnotacja jest przydatna do sprawdzania czasu kompilacji kodu. Nie można mieć więcej niż jedną metodę oprócz static, defaulti metody abstrakcyjne, że metody nadpisywania w Objectna swój @FunctionalInterfacelub inny interfejs wykorzystywany jako interfejs funkcjonalnej.

Ale możesz używać lambd bez tej adnotacji, a także możesz przesłonić metody bez @Overrideadnotacji.

Z dokumentów

funkcjonalny interfejs ma dokładnie jedną abstrakcyjną metodę. Ponieważ metody domyślne mają implementację, nie są abstrakcyjne. Jeśli interfejs deklaruje abstrakcyjną metodę przesłaniającą jedną z publicznych metod java.lang.Object, to również nie wlicza się do liczby metod abstrakcyjnych interfejsu, ponieważ każda implementacja interfejsu będzie miała implementację z java.lang.Object lub z innego miejsca.

To mogą być stosowane do ekspresji lambda:

public interface Foo {
  public void doSomething();
}

Nie można tego użyć w wyrażeniu lambda:

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

Ale to da błąd kompilacji :

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

Nieprawidłowa adnotacja „@FunctionalInterface”; Foo nie jest funkcjonalnym interfejsem


43
Aby być bardziej precyzyjnym, musisz mieć dokładnie jedną abstrakcyjną metodę, która nie przesłania metody w java.lang.Objectfunkcjonalnym interfejsie.
Holger

9
… I jest to trochę inne niż „nie mieć więcej niż jednej publicmetody oprócz statici default”…
Holger

4
Nadal nie rozumiem sensu jej posiadania. Dlaczego ktokolwiek na świecie miałby zawracać sobie głowę sprawdzaniem, ile metod ma jego interfejs. Interfejsy znaczników nadal mają swój punkt i określony cel. Dokumentacja i odpowiedź wyjaśniają tylko, do czego służy, a nie w jakim celu. A „użycie” jest dokładnie tym, o co prosił OP. Więc nie polecałbym tej odpowiedzi.
saran3h

1
@VNT błąd kompilacji pobiera klientów tego interfejsu, ale sam interfejs nie może się zmienić. Dzięki tej adnotacji błąd kompilacji występuje w interfejsie, więc masz pewność, że nikt nie złamie klientów twojego interfejsu.
Sergii Bishyr

2
To pokazuje, jak ich używać, ale nie wyjaśnia, dlaczego ich potrzebujemy.
szejk

14

Dokumentacja sprawia, że rzeczywiście różnicy między celów

Informacyjny typ adnotacji używany do wskazania, że ​​deklaracja typu interfejsu ma być interfejsem funkcjonalnym zdefiniowanym w specyfikacji języka Java.

i przypadek użycia

Należy pamiętać, że wystąpienia funkcjonalnych interfejsów można tworzyć za pomocą wyrażeń lambda, odwołań do metod lub odwołań do konstruktorów.

którego sformułowanie nie wyklucza ogólnie innych przypadków użycia. Ponieważ głównym celem jest wskazanie funkcjonalnego interfejsu , twoje pytanie sprowadza się do „Czy istnieją inne przypadki użycia interfejsów funkcjonalnych inne niż wyrażenia lambda i odwołania do metod / konstruktorów?”

Ponieważ interfejs funkcjonalny jest konstrukcją języka Java zdefiniowaną w specyfikacji języka Java, tylko ta specyfikacja może odpowiedzieć na to pytanie:

JLS §9.8. Funkcjonalne interfejsy :

Oprócz zwykłego procesu tworzenia instancji interfejsu przez deklarowanie i tworzenie instancji klasy (§15.9), instancje interfejsów funkcjonalnych można tworzyć za pomocą wyrażeń referencyjnych do metod i wyrażeń lambda (§15.13, §15.27).

Zatem specyfikacja języka Java nie mówi inaczej, jedyny przypadek użycia wspomniany w tej sekcji to tworzenie instancji interfejsu z wyrażeniami odwołań do metod i wyrażeniami lambda. (Obejmuje to odniesienia do konstruktorów, ponieważ są one wymienione jako jedna z form wyrażenia odwołania do metody w specyfikacji).

Więc jednym zdaniem nie, nie ma innego przypadku użycia tego w Javie 8.


Może po prostu poprosić o trochę za dużo lub nieistotne (możesz nie odpowiadać), ale co byś zasugerował, gdy ktoś stworzył narzędzie, public static String generateTaskId()a co by było bardziej „funkcjonalne”, ktoś inny zdecydował się napisać to tak, jak public class TaskIdSupplier implements Supplier<String>w przypadku getmetody wykorzystującej wdrożenie istniejącej generacji. Czy to niewłaściwe użycie funkcjonalnych interfejsów, zwłaszcza ponowne użycie Supplierwbudowanego JDK? PS: Nie mogłem znaleźć lepszego miejsca / pytań i odpowiedzi, aby o to zapytać. Chętnie przeprowadzę migrację, jeśli możesz to zasugerować.
Naman

1
@Naman, nie sprawiasz, że metoda narzędzia będzie bardziej funkcjonalna podczas tworzenia nazwanej klasy TaskIdSupplier. Teraz pytanie brzmi, dlaczego utworzyłeś nazwaną klasę. Istnieją scenariusze, w których taki nazwany typ jest potrzebny, np. Gdy chcesz wspierać znajdowanie implementacji za pośrednictwem ServiceLoader. Nie ma nic złego w pozwoleniu mu na wdrożenie Supplier. Ale kiedy tego nie potrzebujesz, nie twórz tego. Gdy potrzebujesz tylko a Supplier<String>, już wystarczy użyć DeclaringClass::generateTaskIdi wyeliminować potrzebę jawnej klasy, co jest celem tej funkcji języka.
Holger

Szczerze mówiąc, szukałem uzasadnienia dla rekomendacji, którą przekazałem. Z jakiegoś powodu w pracy tak naprawdę nie czułem, że TaskIdSupplierimplementacja jest warta wysiłku, ale wtedy pomysł ServiceLoadertotalnie zapomniał. Napotkał na kilka pytań w trakcie tych dyskusji byliśmy mających takie jak Co jest zastosowanie Supplier„s publicistnienia, kiedy można iść do przodu i rozwijać własne interfejsy? i dlaczego nie mieć public static Supplier<String> TASK_ID_SUPPLIER = () ->...jako globalnej stałej? . (1/2)
Naman

1
@Naman idiomatycznym sposobem przedstawiania funkcji w Javie są metody, a ocena tych funkcji jest identyczna z ich wywoływaniem. Nigdy nie należy zmuszać programisty do tego variable.genericMethodName(args)zamiast meaningfulMethodName(args). Używanie typu klasy do reprezentowania funkcji, czy to za pośrednictwem wyrażenia lambda / odwołania do metody, czy ręcznie utworzonej klasy, jest jedynie narzędziem do przekazywania funkcji (w przypadku braku prawdziwych typów funkcji w Javie). Należy to robić tylko w razie potrzeby.
Holger

1
Kiedy masz tylko przekazywany mały fragment kodu, możesz utworzyć wyrażenie lambda, które go hermetyzuje. Ilekroć zachodzi potrzeba wywołania go jak metody (dotyczy to scenariuszy wymagających testowania, gdy fragment kodu nie jest trywialny), utwórz nazwaną metodę, którą można wywołać i użyj odwołania do metody lub wyrażenia lambda / jawnej klasy hermetyzowanie połączenia, aby przekazać je w razie potrzeby. Stałe są przydatne tylko wtedy, gdy nie ufasz wydajności wyrażeń lambda lub odwołań do metod osadzonych w kodzie, innymi słowy, prawie nigdy nie są potrzebne.
Holger

12

Jak powiedzieli inni, interfejs funkcjonalny to interfejs, który ujawnia jedną metodę. Może mieć więcej niż jedną metodę, ale wszystkie inne muszą mieć domyślną implementację. Powodem, dla którego nazywa się go „interfejsem funkcjonalnym”, jest fakt, że skutecznie działa jako funkcja. Ponieważ jako parametry można przekazywać interfejsy, oznacza to, że funkcje są teraz „obywatelami pierwszej klasy”, jak w funkcjonalnych językach programowania. Ma to wiele zalet, a zobaczysz je dość często podczas korzystania z interfejsu Stream API. Oczywiście wyrażenia lambda są ich głównym oczywistym zastosowaniem.


10

Ani trochę. Wyrażenia lambda są jedynym punktem tej adnotacji.


6
Cóż, lamdbas działa również bez adnotacji. Jest to stwierdzenie podobne @Overridedo tego, aby dać znać kompilatorowi, że zamierzałeś napisać coś, co było „funkcjonalne” (i otrzymać błąd, jeśli się poślizgniesz).
Thilo

1
Prosto do rzeczy i poprawna odpowiedź, choć trochę krótka. Poświęciłem trochę czasu, aby dodać bardziej rozbudowaną odpowiedź, mówiąc to samo z większą liczbą słów…
Holger

5

Wyrażenie lambda można przypisać do typu interfejsu funkcjonalnego, ale także odwołania do metod i klasy anonimowe.

Jedną miłą rzeczą specyficznych interfejsów funkcjonalnych w java.util.functionto, że mogą one składać się stworzyć nowe funkcje (jak Function.andTheni Function.compose, Predicate.anditp) ze względu na poręczne metod domyślnych, które zawierają.


Powinieneś bardziej rozwinąć ten komentarz. A co z odwołaniami do metod i nowymi funkcjami?
K.Nicholas

5

Interfejs z tylko jedną abstrakcyjną metodą nazywany jest interfejsem funkcjonalnym. Korzystanie z @FunctionalInterface nie jest obowiązkowe, ale najlepiej jest używać go z interfejsami funkcjonalnymi, aby uniknąć przypadkowego dodania dodatkowych metod. Jeśli interfejs jest oznaczony adnotacją @FunctionalInterface i próbujemy mieć więcej niż jedną metodę abstrakcyjną, zgłasza błąd kompilatora.

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

Interfejs funkcjonalny:

  • Wprowadzony w Javie 8
  • Interfejs zawierający metodę „pojedynczej abstrakcji”.

Przykład 1:

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

Przykład 2:

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

Przykład 3:

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

Adnotacja Java8 - @FunctionalInterface

  • Sprawdź adnotacje, czy interfejs zawiera tylko jedną abstrakcyjną metodę. Jeśli nie, zgłoś błąd.
  • Mimo że brakuje @FunctionalInterface, nadal jest to funkcjonalny interfejs (jeśli ma jedną metodę abstrakcyjną). Adnotacja pomaga uniknąć błędów.
  • Funkcjonalny interfejs może mieć dodatkowe metody statyczne i domyślne.
  • np. Iterowalny <>, Porównywalny <>, Komparator <>.

Zastosowania interfejsu funkcjonalnego:

  • Odniesienia do metod
  • Wyrażenie lambda
  • Referencje konstruktora

Aby nauczyć się interfejsów funkcjonalnych, nauczyć się pierwszych metod domyślnych w interfejsie, a po nauczeniu się interfejsu funkcjonalnego będzie Ci łatwo zrozumieć odniesienie do metody i wyrażenie lambda


Czy pierwsze dwa przykłady powinny zawierać słowo kluczowe „abstrakcyjne”?
sofs 1

1
@ sofs1 Metody zadeklarowane w interfejsach są domyślnie publiczne i abstrakcyjne. Musisz użyć słowa kluczowego abstract w przypadku metod w klasie abstrakcyjnej. Jednak dobrze jest również używać słowa kluczowego abstract dla metod w interfejsie. Zezwolili na to dla kompatybilności ze starszą wersją Java, ale jest to odradzane.
Ketan

2

Możesz użyć lambdy w Javie 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");
}

Więcej informacji na temat Java Lambdas i FunctionalInterfaces


1

@FunctionalInterface to nowa adnotacja, która została wydana wraz z Javą 8 i zawiera typy docelowe dla wyrażeń lambda i jest używana do sprawdzania czasu kompilacji kodu.

Kiedy chcesz go użyć:

1- Twój interfejs nie może mieć więcej niż jednej metody abstrakcyjnej, w przeciwnym razie zostanie podany błąd kompilacji.

1- Twój interfejs Powinien być czysty, co oznacza, że ​​interfejs funkcjonalny ma być implementowany przez klasy bezstanowe, na przykład czysty to Comparatorinterfejs, ponieważ nie zależy od stanu implementującego, w tym przypadku nie zostanie podany żaden błąd kompilacji, ale w wielu przypadkach nie będzie w stanie używać lambdy z tego rodzaju interfejsami

java.util.functionPakiet zawiera różne funkcjonalne interfejsy ogólnego zastosowania, takie jak Predicate, Consumer, Function, i Supplier.

Pamiętaj również, że możesz używać lambd bez tej adnotacji.


1

Oprócz innych odpowiedzi, myślę, że głównym powodem, dla którego „dlaczego używanie interfejsu funkcjonalnego innego niż bezpośrednio z wyrażeniami lambda” może być związane z naturą języka Java, który jest zorientowany obiektowo.

Główne atrybuty wyrażeń Lambda to: 1. Można je przekazywać 2. i wykonywać w przyszłości w określonym czasie (kilka razy). Aby obsługiwać tę funkcję w językach, niektóre inne języki po prostu zajmują się tą sprawą.

Na przykład w Java Script funkcja (funkcja anonimowa lub literały funkcyjne) może być adresowana jako obiekt. Możesz więc je łatwo utworzyć, a także przypisać je do zmiennej i tak dalej. Na przykład:

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

lub przez ES6, możesz użyć funkcji strzałki.

const myFunction = ... => ...

Do tej pory projektanci języka Java nie akceptowali obsługi wspomnianych funkcji w ten sposób (techniki programowania funkcjonalnego). Uważają, że język Java jest zorientowany obiektowo i dlatego powinni rozwiązać ten problem za pomocą technik zorientowanych obiektowo. Nie chcą przegapić prostoty i spójności języka Java.

Dlatego używają interfejsów, ponieważ gdy obiekt interfejsu z tylko jedną metodą (mam na myśli interfejs funkcjonalny) jest potrzebny, można go zastąpić wyrażeniem lambda. Jak na przykład:

ActionListener listener = event -> ...;
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.