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("Я — fancy операция!");
}
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 и случайно портите интерфейс. Если не пометить интерфейс аннотацией, можно случайно добавить лишний метод — и потом долго искать, почему код не работает. Лучше всегда добавлять аннотацию для явности.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ