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

Какие знаешь способы передачи лямбда функции в Optional?

1.3 Junior🔥 141 комментариев
#Stream API и функциональное программирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Способы передачи лямбда функций в Optional

Optional (Java 8+) - мощный инструмент для работы с null-значениями. Поддерживает функциональное программирование через лямбда-функции.

1. map() - трансформация значения

Применяет функцию к значению если оно присутствует:

Optional<String> name = Optional.of("John");

// Лямбда функция: String -> String
Optional<Integer> length = name.map(s -> s.length());
// Результат: Optional[4]

// Цепочка map
Optional<String> result = name
    .map(String::toUpperCase)        // JOHN
    .map(s -> s.substring(0, 2))     // JO
    .map(s -> "Hello " + s);         // Hello JO

System.out.println(result.orElse("N/A"));  // Hello JO

Важно: Если Optional пустой, map пропускает функцию:

Optional<String> empty = Optional.empty();
Optional<Integer> result = empty.map(String::length);
// Результат: Optional.empty (функция не выполнилась)

2. flatMap() - для функций возвращающих Optional

Используй когда функция возвращает Optional:

// Без flatMap - вложенные Optional
Optional<User> user = getUserById(1);
Optional<Optional<Address>> address = user.map(u -> u.getAddress());
// Тип: Optional<Optional<Address>> - неудобно!

// С flatMap - развёртывание
Optional<Address> address = getUserById(1)
    .flatMap(user -> user.getAddress());  // Optional<Address>

// Цепочка flatMap
Optional<String> city = getUserById(1)
    .flatMap(user -> user.getAddress())        // Optional<Address>
    .flatMap(addr -> addr.getCity())           // Optional<String>
    .map(String::toUpperCase);                 // Optional<String>

System.out.println(city.orElse("Unknown"));  // MOSCOW

Практический пример:

// Поиск пользователя и его города
public Optional<String> getUserCity(Long userId) {
    return userRepository.findById(userId)
        .flatMap(user -> Optional.ofNullable(user.getAddress()))
        .flatMap(address -> Optional.ofNullable(address.getCity()))
        .map(String::trim);  // Удаляем пробелы
}

// Использование
userRepository.findById(1)
    .flatMap(user -> user.getOrders().stream().findFirst())
    .map(order -> order.getTotal())
    .ifPresentOrElse(
        total -> System.out.println("Total: " + total),
        () -> System.out.println("No orders")
    );

3. filter() - условная фильтрация

Применяет лямбда-предикат:

Optional<User> user = Optional.of(new User("John", 25));

// Фильтр: если возраст >= 18
Optional<User> adult = user.filter(u -> u.getAge() >= 18);
// Результат: Optional[User]

Optional<User> underage = user.filter(u -> u.getAge() < 18);
// Результат: Optional.empty

// Цепочка фильтров
Optional<User> validUser = user
    .filter(u -> u.getAge() >= 18)      // Проверка возраста
    .filter(u -> u.getEmail() != null)  // Проверка email
    .filter(u -> u.isActive());         // Проверка статуса

if (validUser.isPresent()) {
    System.out.println("Valid user found");
}

4. ifPresent() и ifPresentOrElse()

Выполнение функции на основе наличия значения:

Optional<User> user = getUserById(1);

// ifPresent - выполнить если есть
user.ifPresent(u -> System.out.println("User: " + u.getName()));

// Эквивалент без Optional
if (user.isPresent()) {
    System.out.println("User: " + user.get().getName());
}

// ifPresentOrElse - две ветки (Java 9+)
user.ifPresentOrElse(
    u -> System.out.println("Found: " + u.getName()),
    () -> System.out.println("User not found")
);

// Сложный пример
getOrderById(123).ifPresentOrElse(
    order -> {
        order.setStatus("PROCESSING");
        saveOrder(order);
        sendNotification(order.getCustomerId(), "Order received");
    },
    () -> {
        logger.warn("Order 123 not found");
        throw new OrderNotFoundException();
    }
);

5. or() - альтернативное значение (Java 9+)

Возвращает другой Optional если первый пуст:

Optional<User> primaryUser = getUserById(1);
Optional<User> backupUser = getUserById(2);

// Если primary пуст, используй backup
Optional<User> result = primaryUser.or(() -> backupUser);

// Цепочка or
Optional<User> user = getUserById(1)
    .or(() -> getUserById(2))
    .or(() -> getUserById(3))
    .or(() -> Optional.of(getDefaultUser()));

6. Комбинирование нескольких Optional

public record Transaction(
    String userId,
    String accountId,
    BigDecimal amount
) {}

// Классический способ
Optional<User> user = findUser(id);
Optional<Account> account = findAccount(id);
Optional<BigDecimal> limit = findLimit(id);

if (user.isPresent() && account.isPresent() && limit.isPresent()) {
    Transaction tx = new Transaction(
        user.get().getId(),
        account.get().getId(),
        limit.get()
    );
}

// С функциональным стилем (Java 8 способ)
Optional<Transaction> tx = user.flatMap(u ->
    account.flatMap(a ->
        limit.map(l -> new Transaction(u.getId(), a.getId(), l))
    )
);

tx.ifPresent(this::processTransaction);

// С Java 9+ (combinators библиотека)
Optional<Transaction> result = 
    Optional.of(Transaction::new)
        .flatMap(constructor -> user.map(constructor::apply))
        .flatMap(f -> account.map(a -> f.apply(a.getId())))
        .flatMap(f -> limit.map(f::apply));

7. peek() - побочные эффекты для отладки

Выполняет функцию без изменения значения:

User user = getUserById(1)
    .peek(u -> System.out.println("Found user: " + u.getName()))
    .peek(u -> logger.debug("Age: " + u.getAge()))
    .peek(u -> metrics.recordUserAccess(u.getId()))
    .filter(u -> u.getAge() >= 18)
    .peek(u -> System.out.println("Verified adult"))
    .orElse(null);

// Идеально для debugging
Optional<String> result = Optional.of("hello")
    .peek(s -> System.out.println("Step 1: " + s))
    .map(String::toUpperCase)
    .peek(s -> System.out.println("Step 2: " + s))
    .map(s -> s.substring(0, 2))
    .peek(s -> System.out.println("Step 3: " + s));

8. Условные лямбды в комплексных сценариях

// Валидация и обработка
public boolean isValidOrder(String orderId) {
    return findOrder(orderId)
        .filter(order -> order.getStatus() == OrderStatus.PENDING)
        .filter(order -> order.getTotalAmount().compareTo(BigDecimal.ZERO) > 0)
        .filter(order -> isAddressValid(order.getDeliveryAddress()))
        .isPresent();
}

// Трансформация с валидацией
public Optional<OrderDTO> getValidatedOrder(String orderId) {
    return findOrder(orderId)
        .filter(order -> order.getStatus() == OrderStatus.CONFIRMED)
        .map(order -> {
            OrderDTO dto = new OrderDTO();
            dto.setId(order.getId());
            dto.setTotal(order.calculateTotal());
            dto.setEstimatedDelivery(order.getEstimatedDelivery());
            return dto;
        })
        .filter(dto -> dto.getTotal().compareTo(new BigDecimal("100")) >= 0);
}

// Обработка с логированием
public String processUser(Long userId) {
    return findUser(userId)
        .peek(user -> logger.info("Found user: {}", user.getName()))
        .filter(user -> user.isActive())
        .peek(user -> metrics.recordActiveUser(user.getId()))
        .map(User::getEmail)
        .peek(email -> logger.debug("Processing email: {}", email))
        .map(this::sendEmail)
        .map(result -> "Email sent successfully")
        .orElse("User not found or inactive");
}

9. Stream + Optional комбинация

// Получить первый активный пользователь
Optional<User> firstActive = users.stream()
    .filter(User::isActive)
    .findFirst();

// С дополнительной обработкой
List<String> emails = users.stream()
    .map(User::getEmail)
    .filter(Optional::isPresent)
    .map(Optional::get)
    .collect(Collectors.toList());

// Более правильный способ (Java 16+)
List<String> emails = users.stream()
    .map(User::getEmail)
    .flatMap(Optional::stream)  // Optional.stream() (Java 9+)
    .collect(Collectors.toList());

10. Pattern Matching (Java 16+)

// Будущее Java
Object obj = Optional.of("hello");

if (obj instanceof Optional<String> opt && opt.isPresent()) {
    String value = opt.get();
    System.out.println("String: " + value);
}

Шпаргалка: Какую операцию использовать

Операция        Сигнатура                    Когда использовать
───────────────────────────────────────────────────────────────
map()           T -> R                        Трансформация значения
flatMap()       T -> Optional<R>              Вложенные Optional
filter()        T -> boolean                  Условная фильтрация
ifPresent()     T -> void                     Выполнение действия
peek()          T -> void                     Отладка/логирование
or()            () -> Optional<T>             Альтернатива
orElse()        T defaultValue                Дефолтное значение
orElseGet()     () -> T                       Lazy дефолтное значение
orElseThrow()   () -> Exception               Исключение при пусто

Рекомендации

  • Используй flatMap() для вложенных Optional - избегай Optional<Optional<T>>
  • Цепуй операции - вместо множественных if
  • filter() перед map() - более читаемо
  • ifPresentOrElse() вместо if-else - функциональный стиль
  • Избегай .get() без проверки - опасно!
  • peek() только для отладки - не для логики
  • Stream<Optional> -> flatMap(Optional::stream) - Java 9+ best practice
Какие знаешь способы передачи лямбда функции в Optional? | PrepBro