← Назад к вопросам

Какую задачу решают функциональные интерфейсы?

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

Выводы

  1. Функциональные интерфейсы решают задачу передачи поведения как параметра функции

  2. Основные встроенные интерфейсы:

    • Consumer<T> — принимает значение
    • Predicate<T> — проверяет условие
    • Function<T,R> — преобразует
    • Supplier<T> — генерирует значение
  3. Lambda выражения делают код компактным:

    // Вместо анонимного класса
    new Consumer<String>() {
        public void accept(String s) { ... }
    }
    // Пишем просто
    s -> { ... }
    
  4. Функциональный стиль делает код:

    • Более читаемым
    • Более гибким
    • Более переиспользуемым
    • Более тестируемым
  5. Используй их для:

    • Stream API
    • Callbacks
    • Стратегии обработки
    • Configuration
    • Composable operations