CodeGym /Kursy /JAVA 25 SELF /Interfejsy funkcyjne: @FunctionalInterface

Interfejsy funkcyjne: @FunctionalInterface

JAVA 25 SELF
Poziom 20, Lekcja 3
Dostępny

1. Wprowadzenie

Interfejs funkcyjny — to interfejs, który zawiera dokładnie jedną metodę abstrakcyjną (czyli metodę bez implementacji). Właśnie taki interfejs można wykorzystać do skróconego zapisu implementacji metody — w Javie robi się to za pomocą wyrażeń lambda (poznamy je później).

Dlaczego tylko jedna metoda?

Ponieważ interfejs funkcyjny opisuje dokładnie jedną operację. Gdyby metod było dwie lub więcej, stałoby się niejasne, którą z nich należy zaimplementować. Dlatego zasada jest prosta: jeden interfejs — jedna metoda abstrakcyjna.

Przykłady ze standardowej biblioteki

  • Runnable — do zadań w wątkach (void run())
  • Callable<V> — do zadań zwracających wynik (V call())
  • Comparator<T> — do porównywania obiektów (int compare(T o1, T o2))
  • Consumer<T> — „konsument” wartości (void accept(T t))
  • Supplier<T> — „dostawca” wartości (T get())
  • Function<T, R> — funkcja z T do R (R apply(T t))
  • Predicate<T> — sprawdzenie warunku (boolean test(T t))

Oto na przykład interfejs Runnable:

public interface Runnable {
    void run();
}

A oto interfejs Comparator:

public interface Comparator<T> {
    int compare(T o1, T o2);
    // ... są też metody default i static, ale metoda abstrakcyjna jest tylko jedna!
}

Ważna uwaga: metody default i static nie są liczone jako abstrakcyjne, więc może ich być dowolnie wiele!

2. Adnotacja @FunctionalInterface

Java to surowa i zasadnicza dama. Aby uniknąć nieporozumień, pozwala jawnie oznaczyć interfejs jako funkcyjny za pomocą adnotacji @FunctionalInterface. To jak naklejka „Działa tylko z jednym przyciskiem!” — żeby nikt nie dodał nic ponad to.

@FunctionalInterface
public interface Operation {
    int apply(int a, int b);
}

Teraz jeśli przypadkiem zapomnisz i dodasz drugą metodę abstrakcyjną, kompilator natychmiast zgłosi błąd:

@FunctionalInterface
public interface Oops {
    void doIt();
    void doSomethingElse(); // Błąd! Dwie metody abstrakcyjne
}

Czy adnotacja jest obowiązkowa?

Nie, nie jest to konieczne. Interfejs będzie funkcyjny i bez niej, jeśli zawiera dokładnie jedną metodę abstrakcyjną. Jednak dzięki adnotacji wyraźnie pokazujesz swoje zamiary i chronisz się przed przypadkowymi błędami.

Czy można dodawać metody default i static?

Tak, można! Najważniejsze, aby była tylko jedna metoda abstrakcyjna. Wszystkie pozostałe metody mogą być default lub static, ile tylko chcesz.

Przykład:

@FunctionalInterface
public interface FancyOperation {
    int apply(int a, int b);

    default void printInfo() {
        System.out.println("Jestem fancy operacją!");
    }

    static void description() {
        System.out.println("Interfejs funkcyjny do arytmetyki.");
    }
}

3. Przykłady deklaracji i użycia

Załóżmy, że chcesz opisać operację na dwóch liczbach. Robi się to tak:

@FunctionalInterface
public interface Operation {
    int apply(int a, int b);
}

Teraz ten interfejs można zaimplementować na różne sposoby.

Implementacja za pomocą zwykłej klasy

public class SumOperation implements Operation {
    @Override
    public int apply(int a, int b) {
        return a + b;
    }
}

Użycie:

Operation sum = new SumOperation();
System.out.println(sum.apply(2, 3)); // 5

Implementacja za pomocą klasy anonimowej

Operation multiply = new Operation() {
    @Override
    public int apply(int a, int b) {
        return a * b;
    }
};
System.out.println(multiply.apply(2, 3)); // 6

Uwaga o lambdach

Od Javy 8 takie interfejsy wygodnie implementuje się za pomocą wyrażeń lambda — to krótsza składnia. Lambdy omówimy za kilka wykładów, więc na razie wystarczy wiedzieć: interfejsy funkcyjne powstały właśnie po to, aby pracować z nimi jak najwygodniej.

4. Praktyka: napisz własny interfejs funkcyjny

Zadanie 1. Stwórz własny Action!

Utwórz interfejs Action, który przyjmuje napis i nic nie zwraca. Zaimplementuj go jako klasę anonimową, która wypisuje napis wielkimi literami.

@FunctionalInterface
interface Action {
    void act(String s);
}

public class ActionDemo {
    public static void main(String[] args) {
        Action shout = new Action() {
            @Override
            public void act(String text) {
                System.out.println(text.toUpperCase());
            }
        };

        shout.act("uczę się java!"); // UCZĘ SIĘ JAVA!
    }
}

(Później zobaczymy, jak zapisać to krócej za pomocą wyrażeń lambda.)

Zadanie 2. Filtr liczb

Utwórz interfejs NumberPredicate z metodą boolean test(int n). Zaimplementuj sprawdzenie parzystości za pomocą klasy anonimowej.

@FunctionalInterface
interface NumberPredicate {
    boolean test(int n);
}

public class PredicateDemo {
    public static void main(String[] args) {
        NumberPredicate isEven = new NumberPredicate() {
            @Override
            public boolean test(int n) {
                return n % 2 == 0;
            }
        };

        System.out.println(isEven.test(4)); // true
        System.out.println(isEven.test(7)); // false
    }
}

Zadanie 3. Korzystamy ze standardowych interfejsów

Zamiast własnego interfejsu można użyć gotowego Predicate<Integer>:

import java.util.function.Predicate;

Predicate<Integer> isPositive = new Predicate<Integer>() {
    @Override
    public boolean test(Integer x) {
        return x > 0;
    }
};

System.out.println(isPositive.test(10)); // true
System.out.println(isPositive.test(-5)); // false

Tabela: interfejsy funkcyjne ze standardowej biblioteki

Interfejs Metoda Opis Przykład zastosowania
Runnable
void run()
Zadanie bez argumentów i wyniku Wątki, timery
Callable<V>
V call()
Zadanie z wynikiem Wątki, ExecutorService
Comparator<T>
int compare(T, T)
Porównywanie dwóch obiektów Sortowanie kolekcji
Consumer<T>
void accept(T)
„Konsument” wartości Iterowanie po kolekcji
Supplier<T>
T get()
„Dostawca” wartości Leniwa inicjalizacja, generowanie danych
Function<T, R>
R apply(T)
Funkcja z T do R Przekształcanie danych
Predicate<T>
boolean test(T)
Sprawdzenie warunku Filtrowanie kolekcji

5. Typowe błędy przy pracy z interfejsami funkcyjnymi

Błąd nr 1: dodano drugą metodę abstrakcyjną. Jeśli interfejs ma więcej niż jedną metodę abstrakcyjną, przestaje być funkcyjny. Kompilator (zwłaszcza z @FunctionalInterface) natychmiast zgłosi błąd.

Błąd nr 2: zapomniano, że metody default i static nie są liczone jako abstrakcyjne. Można śmiało dodawać je do interfejsu funkcyjnego — nie naruszy to zasady „jednej metody abstrakcyjnej”.

Błąd nr 3: błędnie zaimplementowano sygnaturę metody. Na przykład interfejs wymaga dwóch argumentów, a napisano metodę tylko z jednym. Zawsze sprawdzaj sygnatury.

Błąd nr 4: brak użycia @FunctionalInterface i przypadkowe zepsucie interfejsu. Jeśli nie oznaczysz interfejsu adnotacją, możesz przypadkowo dodać zbędną metodę — a potem długo szukać, dlaczego kod nie działa. Lepiej zawsze dodać tę adnotację dla jasności.

1
Zadanie
JAVA 25 SELF, poziom 20, lekcja 3
Niedostępne
Twój osobisty powiadamiacz
Twój osobisty powiadamiacz
1
Zadanie
JAVA 25 SELF, poziom 20, lekcja 3
Niedostępne
Checker gry: Wysokie punkty
Checker gry: Wysokie punkty
1
Zadanie
JAVA 25 SELF, poziom 20, lekcja 3
Niedostępne
Walidator tekstu: sprawdzanie długości ciągu znaków
Walidator tekstu: sprawdzanie długości ciągu znaków
1
Zadanie
JAVA 25 SELF, poziom 20, lekcja 3
Niedostępne
Konwerter tekstu z wygodnym wyświetlaniem
Konwerter tekstu z wygodnym wyświetlaniem
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION