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
Таблиця: функціональні інтерфейси зі стандартної бібліотеки
| Інтерфейс | Метод | Опис | Приклад застосування |
|---|---|---|---|
|
|
Завдання без аргументів і без результату | Потоки, таймери |
|
|
Завдання з результатом | Потоки, ExecutorService |
|
|
Порівняння двох об’єктів | Сортування колекцій |
|
|
«Споживач» значення | Обхід колекції |
|
|
«Постачальник» значення | Лінива ініціалізація, генерація даних |
|
|
Функція з T у R | Перетворення даних |
|
|
Перевірка умови | Фільтрація колекцій |
5. Типові помилки під час роботи з функціональними інтерфейсами
Помилка № 1: додали другий абстрактний метод. Якщо в інтерфейсі більш як один абстрактний метод, це вже не функціональний інтерфейс. Компілятор, особливо коли інтерфейс позначено @FunctionalInterface, одразу повідомить про помилку.
Помилка № 2: забули, що default- і static-методи не вважаються абстрактними. Їх можна сміливо додавати у функціональний інтерфейс — це не порушить правило «одного абстрактного методу».
Помилка № 3: некоректно реалізували сигнатуру методу. Наприклад, інтерфейс вимагає двох аргументів, а ви написали метод лише з одним. Завжди перевіряйте сигнатури.
Помилка № 4: не використовуєте @FunctionalInterface і випадково псуєте інтерфейс. Якщо не позначити інтерфейс анотацією, можна випадково додати зайвий метод — і потім довго шукати, чому код не працює. Краще завжди додавати анотацію для ясності.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ