← Назад к вопросам
Какую задачу решают функциональные интерфейсы?
2.0 Middle🔥 211 комментариев
#Stream API и функциональное программирование#ООП#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Функциональные интерфейсы в Java
Краткий ответ
Функциональные интерфейсы решают задачу передачи поведения (кода) как параметра в функцию. Это позволяет писать более гибкий и модульный код, избегая создания анонимных классов и делая код более читаемым через Lambda выражения.
Что такое функциональный интерфейс
Функциональный интерфейс — это интерфейс с одним абстрактным методом.
// ✅ Функциональный интерфейс
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2); // Один абстрактный метод
// Можно иметь default методы
default Comparator<T> reversed() { ... }
}
// ✅ Функциональный интерфейс
@FunctionalInterface
public interface Consumer<T> {
void accept(T t); // Один абстрактный метод
}
// ❌ НЕ функциональный интерфейс
@FunctionalInterface
public interface MyInterface {
void method1();
void method2(); // Два абстрактных метода - ERROR!
}
Основная задача: передача поведения
Проблема без функциональных интерфейсов
// ❌ ПЛОХО - нужно создавать классы для каждого поведения
public class StringFilter {
public static List<String> filterLongerThan5(List<String> items) {
List<String> result = new ArrayList<>();
for (String item : items) {
if (item.length() > 5) {
result.add(item);
}
}
return result;
}
public static List<String> filterStartingWithA(List<String> items) {
List<String> result = new ArrayList<>();
for (String item : items) {
if (item.startsWith("A")) {
result.add(item);
}
}
return result;
}
// ... ещё 50 методов для каждого варианта фильтрации
}
// Используем
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
List<String> long_names = StringFilter.filterLongerThan5(names);
List<String> a_names = StringFilter.filterStartingWithA(names);
Решение с функциональными интерфейсами
// ✅ ХОРОШО - один метод, поведение передаём как параметр
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
public class StringFilter {
public static <T> List<T> filter(List<T> items, Predicate<T> condition) {
List<T> result = new ArrayList<>();
for (T item : items) {
if (condition.test(item)) { // Вызываем переданное поведение
result.add(item);
}
}
return result;
}
}
// Используем с Lambda выражениями
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// Разные поведения в одном методе!
List<String> longNames = StringFilter.filter(
names,
name -> name.length() > 5 // Поведение 1
);
List<String> aNames = StringFilter.filter(
names,
name -> name.startsWith("A") // Поведение 2
);
List<String> shortNames = StringFilter.filter(
names,
name -> name.length() < 4 // Поведение 3
);
Встроенные функциональные интерфейсы
Java предоставляет готовые функциональные интерфейсы в java.util.function:
1. Consumer — принимает значение, ничего не возвращает
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
// Пример: печать элементов
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(
name -> System.out.println(name) // Consumer<String>
);
// Эквивалентно
Consumer<String> printer = name -> System.out.println(name);
names.forEach(printer);
2. Predicate — проверка условия
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
// Пример: фильтрация
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0) // Predicate<Integer>
.collect(Collectors.toList());
// Эквивалентно
Predicate<Integer> isEven = n -> n % 2 == 0;
List<Integer> evenNumbers = numbers.stream()
.filter(isEven)
.collect(Collectors.toList());
3. Function — преобразование (T → R)
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
// Пример: преобразование
List<String> names = Arrays.asList("alice", "bob", "charlie");
List<String> uppercase = names.stream()
.map(n -> n.toUpperCase()) // Function<String, String>
.collect(Collectors.toList());
// Эквивалентно
Function<String, String> toUpper = n -> n.toUpperCase();
List<String> uppercase = names.stream()
.map(toUpper)
.collect(Collectors.toList());
4. Supplier — генерирует значение
@FunctionalInterface
public interface Supplier<T> {
T get();
}
// Пример: ленивая инициализация
Supplier<LocalDateTime> now = () -> LocalDateTime.now();
LocalDateTime time1 = now.get();
// позже...
LocalDateTime time2 = now.get(); // Генерирует новое значение
Практические примеры
Пример 1: Обработка данных с разными стратегиями
public class DataProcessor {
public static <T, R> List<R> process(
List<T> data,
Function<T, R> transformer,
Predicate<R> filter
) {
return data.stream()
.map(transformer) // Function - преобразование
.filter(filter) // Predicate - фильтрация
.collect(Collectors.toList());
}
}
// Использование
List<String> numbers = Arrays.asList("1", "2", "3", "4", "5");
List<Integer> evenNumbers = DataProcessor.process(
numbers,
Integer::parseInt, // Function: String → Integer
n -> n % 2 == 0 // Predicate: Integer → boolean
);
Пример 2: Кэширование с лямбдой
public class LazyCache<T, R> {
private final Map<T, R> cache = new HashMap<>();
private final Function<T, R> loader; // Функция загрузки
public LazyCache(Function<T, R> loader) {
this.loader = loader;
}
public R get(T key) {
// Если в кэше нет - вызываем Function для загрузки
return cache.computeIfAbsent(key, loader);
}
}
// Использование
LazyCache<Long, User> userCache = new LazyCache<>(
userId -> database.getUserById(userId) // Function загружает из БД
);
User user = userCache.get(1L); // Загружает из БД
User user2 = userCache.get(1L); // Возвращает из кэша
Пример 3: Callback и обработка ошибок
public class AsyncService {
private final Executor executor = Executors.newFixedThreadPool(4);
public <T> void executeAsync(
Supplier<T> task, // Что выполнить
Consumer<T> onSuccess, // Что делать при успехе
Consumer<Exception> onError // Что делать при ошибке
) {
executor.execute(() -> {
try {
T result = task.get(); // Выполняем задачу
onSuccess.accept(result); // Успех
} catch (Exception e) {
onError.accept(e); // Ошибка
}
});
}
}
// Использование
AsyncService service = new AsyncService();
service.executeAsync(
() -> database.fetchUser(1L), // Supplier
user -> System.out.println("User: " + user.getName()), // Consumer (успех)
error -> logger.error("Failed", error) // Consumer (ошибка)
);
Пример 4: Stream API
List<Product> products = /* ... */;
// Все функциональные интерфейсы работают в Stream:
List<String> expensiveProductNames = products.stream()
.filter(p -> p.getPrice() > 100) // Predicate
.map(Product::getName) // Function
.sorted() // Comparator (функциональный интерфейс!)
.distinct() // Predicate
.peek(name -> logger.info("Name: " + name)) // Consumer
.limit(10)
.collect(Collectors.toList());
Создание собственных функциональных интерфейсов
// ✅ Правильный способ
@FunctionalInterface
public interface PaymentProcessor {
// Один абстрактный метод
boolean process(Payment payment);
// Можно добавить default методы
default void logPayment(Payment payment) {
System.out.println("Processing: " + payment);
}
// Можно добавить static методы
static PaymentProcessor createDummy() {
return p -> true;
}
}
// Использование
PaymentProcessor creditCardProcessor = payment -> {
// Логика обработки credit card
return creditCardAPI.charge(payment.getAmount());
};
PaymentProcessor paypalProcessor = payment -> {
// Логика обработки PayPal
return paypalAPI.pay(payment.getAmount());
};
// Легко переключаться между стратегиями
public void processPayment(Payment p, PaymentProcessor processor) {
if (processor.process(p)) {
p.setStatus("COMPLETED");
}
}
Композиция функциональных интерфейсов
// Function позволяет цепочку преобразований
Function<Integer, Integer> addOne = n -> n + 1;
Function<Integer, Integer> multiplyByTwo = n -> n * 2;
// Композиция (Java 8+)
Function<Integer, Integer> addThenMultiply = addOne.andThen(multiplyByTwo);
Integer result = addThenMultiply.apply(5); // (5+1)*2 = 12
Function<Integer, Integer> multiplyThenAdd = addOne.compose(multiplyByTwo);
Integer result = multiplyThenAdd.apply(5); // (5*2)+1 = 11
Выводы
-
Функциональные интерфейсы решают задачу передачи поведения как параметра функции
-
Основные встроенные интерфейсы:
Consumer<T>— принимает значениеPredicate<T>— проверяет условиеFunction<T,R>— преобразуетSupplier<T>— генерирует значение
-
Lambda выражения делают код компактным:
// Вместо анонимного класса new Consumer<String>() { public void accept(String s) { ... } } // Пишем просто s -> { ... } -
Функциональный стиль делает код:
- Более читаемым
- Более гибким
- Более переиспользуемым
- Более тестируемым
-
Используй их для:
- Stream API
- Callbacks
- Стратегии обработки
- Configuration
- Composable operations