JavaRush /Курси /JAVA 25 SELF /Функціональні інтерфейси: @FunctionalInterface

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

JAVA 25 SELF
Рівень 20 , Лекція 3
Відкрита

1. Вступ

Функціональний інтерфейс — це інтерфейс із рівно одним абстрактним методом (тобто методом без реалізації). Саме такі інтерфейси зручно використовувати для стислого запису реалізації методу: у Java це роблять за допомогою лямбда-виразів (ми їх вивчимо згодом).

Чому лише один метод?

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

Приклади зі стандартної бібліотеки

  • Runnable — для завдань у потоках (void run())
  • Callable<V> — для завдань, що повертають результат (V call())
  • Comparator<T> — для порівняння об’єктів (int compare(T o1, T o2))
  • Consumer<T> — «споживач» значення (void accept(T t))
  • Supplier<T> — «постачальник» значення (T get())
  • Function<T, R> — функція з T у R (R apply(T t))
  • Predicate<T> — перевірка умови (boolean test(T t))

Наприклад, інтерфейс Runnable виглядає так:

public interface Runnable {
    void run();
}

А ось інтерфейс Comparator:

public interface Comparator<T> {
    int compare(T o1, T o2);
    // ... ще є default- і static-методи, але абстрактний метод лише один!
}

Важливий момент: default- і static-методи не є абстрактними, тож їх може бути скільки завгодно!

2. Анотація @FunctionalInterface

Java — сувора та принципова дама. Щоб уникнути плутанини, інтерфейс можна явно позначити як функціональний за допомогою анотації @FunctionalInterface. Це як наліпка «Працює лише з однією кнопкою!» — щоб ніхто не додав зайвого.

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

Тепер, якщо ви раптом додасте другий абстрактний метод, компілятор одразу повідомить про помилку:

@FunctionalInterface
public interface Oops {
    void doIt();
    void doSomethingElse(); // Помилка! Два абстрактні методи.
}

Чи обов’язкова анотація?

Ні. Інтерфейс буде функціональним і без неї, якщо має рівно один абстрактний метод. Але з анотацією ви чітко фіксуєте намір і захищаєтеся від випадкових помилок.

Чи можна додавати default- і static-методи?

Так, можна! Головне, щоб був лише один абстрактний метод. Усі інші методи можуть бути default або static, скільки завгодно.

Приклад:

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

    default void printInfo() {
        System.out.println("Я — демонстраційна операція!");
    }

    static void description() {
        System.out.println("Функціональний інтерфейс для арифметики.");
    }
}

3. Приклади оголошення та використання

Припустімо, ви хочете описати операцію над двома числами. Ось як це можна зробити:

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

Тепер цей інтерфейс можна реалізувати різними способами.

Реалізація через звичайний клас

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

Використання:

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

Реалізація через анонімний клас

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

Примітка щодо лямбд

Починаючи з Java 8, такі інтерфейси зручно реалізовувати лямбда-виразами — це коротший синтаксис. Ми розглянемо лямбди за кілька лекцій, тож поки достатньо знати: функціональні інтерфейси створено саме для максимально зручної роботи з ними.

4. Практика: напишіть власний функціональний інтерфейс

Завдання 1. Зробіть свій Action!

Створіть інтерфейс Action, який приймає рядок і нічого не повертає. Реалізуйте його через анонімний клас, що виводить рядок у верхньому регістрі.

@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("я вивчаю Java!"); // Я ВИВЧАЮ JAVA!
    }
}

(Згодом ми побачимо, як це можна записати коротше за допомогою лямбда-виразів.)

Завдання 2. Фільтр чисел

Створіть інтерфейс NumberPredicate з методом boolean test(int n). Реалізуйте перевірку на парність за допомогою анонімного класу.

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

Завдання 3. Використовуємо стандартні інтерфейси

Замість власного інтерфейсу можна використати готовий 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

Таблиця: функціональні інтерфейси зі стандартної бібліотеки

Інтерфейс Метод Опис Приклад застосування
Runnable
void run()
Завдання без аргументів і без результату Потоки, таймери
Callable<V>
V call()
Завдання з результатом Потоки, ExecutorService
Comparator<T>
int compare(T, T)
Порівняння двох об’єктів Сортування колекцій
Consumer<T>
void accept(T)
«Споживач» значення Обхід колекції
Supplier<T>
T get()
«Постачальник» значення Лінива ініціалізація, генерація даних
Function<T, R>
R apply(T)
Функція з T у R Перетворення даних
Predicate<T>
boolean test(T)
Перевірка умови Фільтрація колекцій

5. Типові помилки під час роботи з функціональними інтерфейсами

Помилка № 1: додали другий абстрактний метод. Якщо в інтерфейсі більш як один абстрактний метод, це вже не функціональний інтерфейс. Компілятор, особливо коли інтерфейс позначено @FunctionalInterface, одразу повідомить про помилку.

Помилка № 2: забули, що default- і static-методи не вважаються абстрактними. Їх можна сміливо додавати у функціональний інтерфейс — це не порушить правило «одного абстрактного методу».

Помилка № 3: некоректно реалізували сигнатуру методу. Наприклад, інтерфейс вимагає двох аргументів, а ви написали метод лише з одним. Завжди перевіряйте сигнатури.

Помилка № 4: не використовуєте @FunctionalInterface і випадково псуєте інтерфейс. Якщо не позначити інтерфейс анотацією, можна випадково додати зайвий метод — і потім довго шукати, чому код не працює. Краще завжди додавати анотацію для ясності.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ