Термін функціональний інтерфейс з’явився в Java 8 разом із підтримкою lambda виразів. Такий інтерфейс містить лише один абстрактний метод, що дозволяє використовувати його як цільовий тип для лямбда виразу.
Водночас функціональний інтерфейс може містити додаткові методи з реалізацією – default та static.
Розглянемо простий приклад функціонального інтерфейсу Java:
@FunctionalInterface
public interface Greeting {
void sayHello(String name);
}Використання анотації @FunctionalInterface не є обов’язковим, однак це допомагає уникнути помилок. Вона повідомляє компілятор про ваш намір створити функціональний інтерфейс і забезпечує:
- перевірку, що інтерфейс має рівно один абстрактний метод
- повідомлення про помилку, якщо ви випадково додасте другий абстрактний метод до інтерфейсу
Один із поширених способів реалізувати функціональний інтерфейс у Java – використати lambda вираз. Ось як можна реалізувати інтерфейс Greeting з попереднього прикладу:
public class Main {
public static void main(String[] args) {
Greeting greet = (name) -> System.out.println("Hello, " + name + "!");
greet.sayHello("Ryan"); // Hello, Ryan!
}
}Завдяки тому, що Greeting має лише один абстрактний метод, Java розуміє який саме метод реалізує lambda вираз.
У Java лямбда вираз не має власного типу, але його можна використовувати там, де очікується функціональний інтерфейс. Такий інтерфейс виступає цільовим типом (target type) для лямбда виразу.
Розглянемо ще один приклад, для початку визначивши функціональний інтерфейс:
@FunctionalInterface
public interface TextTransformer {
String transform(String input);
}Тепер використаємо TextTransformer у методі:
public class Main {
public static void main(String[] args) {
String original = "java is fun";
// Передаємо лямбду як реалізацію методу transform
String upper = applyTransform(original, text -> text.toUpperCase());
String excited = applyTransform(original, text -> text + "!!!");
System.out.println("Original: " + original); // Original: java is fun
System.out.println("Upper case: " + upper); // Upper case: JAVA IS FUN
System.out.println("Excited: " + excited); // Excited: java is fun!!!
}
// Метод приймає цільовий тип TextTransformer
static String applyTransform(String input, TextTransformer transformer) {
return transformer.transform(input);
}
}У цьому прикладі:
- Метод applyTransform(...) очікує об’єкт типу TextTransformer.
- Ми передаємо лямбда вираз text -> ... у applyTransform(...), і Java розпізнає його як реалізацію методу transform(String), оскільки за контекстом потрібен саме TextTransformer.
- Таким чином, TextTransformer виступає цільовим типом для lambda.
Вбудовані функціональні інтерфейси в Java
Java містить набір стандартних функціональних інтерфейсів для поширених сценаріїв, тож у більшості випадків немає потреби створювати власні.
Далі розглянемо деякі з вбудованих функціональних інтерфейсів Java.
Consumer (споживач)
Consumer – це функціональний інтерфейс, який описує функцію, що приймає один аргумент типу T та виконує з ним певну дію, не повертаючи жодного результату:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}Реалізацію інтерфейсу Consumer доцільно використовувати для операцій над значенням, наприклад для його виведення на екран, запису у файл або передачі по мережі, тощо. Приклад використання Consumer для обробки списку рядків:
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Consumer<String> greeter = name -> System.out.println("Hello, " + name);
names.forEach(greeter); // або names.forEach(name -> System.out.println("Hi, " + name));
}
}Також інтерфейс Consumer має default метод andThen(...), який дозволяє об’єднувати Consumer у ланцюжок:
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("Darina", "Oleh", "Ivan");
// Перший Consumer — привітання
Consumer<String> greet = name -> System.out.print("Hello, " + name);
// Другий Consumer — додавання роздільника
Consumer<String> exclaim = name -> System.out.println("!");
// Об’єднання двох Consumer-ів через andThen()
Consumer<String> combined = greet.andThen(exclaim);
// Застосування комбінованої дії до кожного імені
names.forEach(combined);
}
}
// Hello, Darina!
// Hello, Oleh!
// Hello, Ivan!Supplier (постачальник)
Supplier – функціональний інтерфейс, який описує функцію без аргументів, що просто постачає (повертає) значення:
@FunctionalInterface
public interface Supplier<T> {
T get();
}Supplier можна розглядати як фабричний інтерфейс. Приклад постачальника поточної дати:
import java.time.LocalDate;
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
// Створюємо Supplier, який повертає поточну дату
Supplier<LocalDate> dateSupplier = () -> LocalDate.now();
// Отримуємо значення через метод get()
LocalDate today = dateSupplier.get();
System.out.println("Today's date is: " + today);
}
}
// Today's date is: 2025-07-19Predicate
Predicate – функціональний інтерфейс з пакету java.util.function, який описує функцію, що приймає одне значення та повертає булевий результат (true або false):
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}Predicate широко використовується для фільтрації та перевірки умов. Наприклад під час фільтрації списку:
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("Darina", "", "Oleh", "Ivan", "");
Predicate<String> isNotEmpty = s -> !s.isEmpty();
List<String> filtered = names.stream()
.filter(isNotEmpty)
.collect(Collectors.toList());
System.out.println(filtered); // Виведе: [Darina, Oleh, Ivan]
}
}Тут метод stream().filter(...) приймає Predicate<String> у вигляді лямбда виразу s -> !s.isEmpty(), щоб відсіяти порожні рядки списку. Це класичний приклад практичного використання Predicate у Java Streams.
Function
Function – це функціональний інтерфейс з пакету java.util.function, який представляє функцію, що приймає один один аргумент типу T і повертає значення типу R:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}Function часто використовується для обробки даних (наприклад перетворення, обчислення, форматування, тощо). Приклад обробки даних у Java Streams:
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("Olena", "Max", "Sophia", "Taras");
// Перетворюємо список імен на список їх довжин
Function<String, Integer> getLength = name -> name.length();
List<Integer> lengths = names.stream()
.map(getLength)
.collect(Collectors.toList());
System.out.println(lengths); // [5, 3, 6, 5]
}
}Метод stream().map(...) очікує Function<T, R>, якому відповідає лямбда вираз s -> s.length().
Підсумок
Функціональні інтерфейси дають змогу передавати поведінку як параметр за допомогою лаконічного і простого синтаксису. У Java існує набір стандартних інтерфейсів – Consumer, Supplier, Predicate, Function, які покривають типові сценарії, від фільтрації до обробки даних. Для специфічних завдань можна створювати власні функціональні інтерфейси. Розуміння цієї концепції дозволяє писати чистіший та гнучкіший код.