Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Функциональные интерфейсы в Java: практические примеры и применение
Функциональные интерфейсы (Functional Interfaces) — это один из главных нововведений Java 8, которые открыли дверь к функциональному программированию. Давайте разберемся, что это такое и как их использовать.
Определение функционального интерфейса
Функциональный интерфейс — это интерфейс с ровно одним abstract методом. Он может иметь много default методов, но abstract метод только один.
// Функциональный интерфейс (1 abstract метод)
@FunctionalInterface
public interface Calculator {
int calculate(int a, int b); // Единственный abstract метод
// default методы можно добавлять
default void printResult(int result) {
System.out.println("Result: " + result);
}
}
// Не функциональный интерфейс (2 abstract метода)
public interface NotFunctional {
void method1();
void method2();
// ОШИБКА: много abstract методов
}
Встроенные функциональные интерфейсы (java.util.function)
Java 8 предоставил готовые функциональные интерфейсы в пакете java.util.function:
1. Predicate<T> — проверка условия (true/false)
// Сигнатура: boolean test(T t)
public interface Predicate<T> {
boolean test(T t);
}
// Пример 1: фильтрация по числам
Predicate<Integer> isEven = n -> n % 2 == 0;
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream()
.filter(isEven)
.toList();
// Результат: [2, 4, 6]
// Пример 2: фильтрация пользователей
Predicate<User> isActive = user -> user.isActive();
Predicate<User> isPremium = user -> user.isPremium();
Predicate<User> isActiveAndPremium = isActive.and(isPremium);
List<User> premiumUsers = users.stream()
.filter(isActiveAndPremium)
.toList();
// Пример 3: валидация
Predicate<String> isLongEnough = str -> str.length() >= 8;
Predicate<String> hasDigit = str -> str.matches(".*\\d.*");
Predicate<String> isValidPassword = isLongEnough.and(hasDigit);
if (isValidPassword.test("password123")) {
System.out.println("Valid password");
}
2. Function<T, R> — преобразование (T -> R)
// Сигнатура: R apply(T t)
public interface Function<T, R> {
R apply(T t);
}
// Пример 1: преобразование строк
Function<String, Integer> stringLength = str -> str.length();
Integer length = stringLength.apply("Hello"); // 5
// Пример 2: преобразование объектов
Function<User, UserDTO> userToDTO = user -> new UserDTO(
user.getId(),
user.getName(),
user.getEmail()
);
List<UserDTO> dtos = users.stream()
.map(userToDTO)
.toList();
// Пример 3: цепочка преобразований
Function<String, String> trim = str -> str.trim();
Function<String, String> toUpperCase = str -> str.toUpperCase();
Function<String, Integer> parseInteger = Integer::parseInt;
Function<String, Integer> composed = trim.andThen(toUpperCase).andThen(parseInteger);
Integer result = composed.apply(" 42 "); // 42
// Пример 4: в коллекциях
Map<String, Integer> ages = new HashMap<>();
ages.put("John", 25);
ages.put("Jane", 30);
Function<Integer, String> ageToCategory = age -> age < 30 ? "young" : "adult";
Map<String, String> categories = ages.entrySet().stream()
.collect(toMap(
Map.Entry::getKey,
e -> ageToCategory.apply(e.getValue())
));
// {John=young, Jane=adult}
3. Consumer<T> — потребление (выполнение действия)
// Сигнатура: void accept(T t)
public interface Consumer<T> {
void accept(T t);
}
// Пример 1: печать элементов
Consumer<String> printer = str -> System.out.println(str);
List<String> fruits = List.of("apple", "banana", "orange");
fruits.forEach(printer);
// Пример 2: логирование
Consumer<User> logger = user -> log.info("User processed: {}", user.getName());
users.forEach(logger);
// Пример 3: отправка уведомлений
Consumer<Order> notifier = order -> emailService.sendConfirmation(order);
orders.stream()
.filter(order -> order.isPaid())
.forEach(notifier);
// Пример 4: цепочка действий
Consumer<String> uppercase = str -> System.out.println(str.toUpperCase());
Consumer<String> lowercase = str -> System.out.println(str.toLowerCase());
Consumer<String> chained = uppercase.andThen(lowercase);
chained.accept("Hello"); // HELLO, hello
4. Supplier<T> — поставщик (создание)
// Сигнатура: T get()
public interface Supplier<T> {
T get();
}
// Пример 1: ленивое вычисление
Supplier<LocalDateTime> now = LocalDateTime::now;
LocalDateTime time1 = now.get();
LocalDateTime time2 = now.get();
// каждый раз возвращает текущее время
// Пример 2: создание объектов
Supplier<StringBuilder> stringBuilderSupplier = StringBuilder::new;
StringBuilder sb1 = stringBuilderSupplier.get();
StringBuilder sb2 = stringBuilderSupplier.get();
// Разные объекты!
// Пример 3: фабрика с параметром
Supplier<User> anonymousUserSupplier = () -> new User("Anonymous", null);
User guest = anonymousUserSupplier.get();
// Пример 4: конфигурация
Supplier<DatabaseConnection> connectionSupplier = () -> {
Properties props = new Properties();
props.load(new FileInputStream("db.properties"));
return new DatabaseConnection(props);
};
DatabaseConnection conn = connectionSupplier.get();
// Пример 5: Optional.orElseGet
Optional<User> user = findUser("john");
User result = user.orElseGet(() -> new User("default", null));
5. UnaryOperator<T> — унарная операция (T -> T)
// Расширяет Function<T, T>
public interface UnaryOperator<T> extends Function<T, T> {}
// Пример 1: преобразование в себе
UnaryOperator<Integer> square = n -> n * n;
Integer result = square.apply(5); // 25
// Пример 2: преобразование списка
UnaryOperator<String> trim = String::trim;
List<String> words = List.of(" hello ", " world ");
words = words.stream()
.map(trim)
.toList();
// Пример 3: для чисел
UnaryOperator<Double> negate = n -> -n;
Double price = 100.0;
Double discount = negate.apply(price); // -100.0
6. BinaryOperator<T> — бинарная операция (T, T -> T)
// Расширяет BiFunction<T, T, T>
public interface BinaryOperator<T> extends BiFunction<T, T, T> {}
// Пример 1: сумма
BinaryOperator<Integer> sum = Integer::sum; // или (a, b) -> a + b
Integer result = sum.apply(3, 4); // 7
// Пример 2: максимум
BinaryOperator<Integer> max = Integer::max;
Integer maxValue = max.apply(10, 20); // 20
// Пример 3: для строк
BinaryOperator<String> concat = String::concat;
String result = concat.apply("Hello", " World"); // Hello World
// Пример 4: в reduce
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
Integer sum = numbers.stream()
.reduce(0, BinaryOperator.identity()); // 1+2+3+4+5 = 15
// Пример 5: кастомная операция
BinaryOperator<User> merge = (u1, u2) -> {
u1.addFriends(u2.getFriends());
return u1;
};
Собственные функциональные интерфейсы
// Кастомный функциональный интерфейс для комплексной логики
@FunctionalInterface
public interface OrderProcessor {
void process(Order order, PaymentGateway gateway, NotificationService notifier);
}
// Использование
OrderProcessor processor = (order, gateway, notifier) -> {
if (gateway.charge(order.getAmount())) {
order.markAsPaid();
notifier.sendConfirmation(order);
} else {
notifier.sendFailure(order);
}
};
// Применение
processor.process(myOrder, paymentGateway, emailService);
Практический пример: Stream API
@Service
public class UserService {
private final UserRepository userRepository;
public List<UserDTO> getActiveUsersSorted() {
return userRepository.findAll().stream()
// filter использует Predicate
.filter(user -> user.isActive())
.filter(user -> user.getCreatedAt().isAfter(LocalDateTime.now().minusMonths(1)))
// map использует Function
.map(user -> new UserDTO(
user.getId(),
user.getName().toUpperCase(),
user.getEmail()
))
// sorted использует Comparator (BinaryOperator)
.sorted(Comparator.comparing(UserDTO::getName))
// collect использует Supplier, Consumer
.toList();
}
public void notifyUsers() {
userRepository.findAll().stream()
.filter(user -> user.hasNewMessages())
// forEach использует Consumer
.forEach(user -> emailService.send(
user.getEmail(),
"You have new messages"
));
}
}
Method Reference (сокращенная запись)
// Вместо lambda можно использовать method reference
// Вместо этого:
List<String> words = List.of("hello", "world");
words.forEach(str -> System.out.println(str));
// Можно писать так:
words.forEach(System.out::println);
// Вместо этого:
List<Integer> numbers = List.of(1, 2, 3);
UnaryOperator<Integer> square = n -> n * n;
// Используй:
Function<Integer, Integer> square = n -> n * n;
// Или используй constructor reference:
Supplier<User> userSupplier = User::new;
// Типы method reference:
User::getName // instance method
User::new // constructor
Integer::parseInt // static method
List::add // method of arbitrary object
Вывод
Функциональные интерфейсы — это:
- Predicate<T> — проверка условия (boolean test)
- Function<T, R> — преобразование (apply)
- Consumer<T> — действие (accept)
- Supplier<T> — создание (get)
- UnaryOperator<T> — унарная операция (T -> T)
- BinaryOperator<T> — бинарная операция (T, T -> T)
Они делают код:
- Более функциональным — легче работать с потоками данных
- Более читаемым — lambda выражения лучше, чем anonymous классы
- Более переиспользуемым — одна функция используется во многих местах
- Более декларативным — описываем ЧТО делать, не КАК
В современной Java функциональные интерфейсы — это стандартный способ работы с callbacks, асинхронностью и трансформациями данных.