Преобразование Optional: map и flatMap
Объединяйте преобразования в цепочки с помощью map и flatMap, обрабатывая значения без проверок на null.
«Преобразование Optional: map и flatMap» — бесплатный урок Java Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.
Преобразование Optional: map и flatMap
map() преобразует значение внутри Optional, если оно присутствует. flatMap() предотвращает появление вложенных Optional, когда само преобразование возвращает Optional.
map: преобразование значения
map(Function) применяет функцию к присутствующему значению и оборачивает результат в новый Optional. Если исходный Optional пуст, возвращается пустой Optional.
import java.util.Optional;
Optional<String> name = Optional.of("alice");
Optional<String> upper = name.map(String::toUpperCase);
System.out.println(upper.get()); // ALICE
Optional<Integer> length = name.map(String::length);
System.out.println(length.get()); // 5
// Empty in → empty out
Optional<String> empty = Optional.<String>empty().map(String::toUpperCase);
System.out.println(empty.isPresent()); // falseЦепочки вызовов map
Несколько вызовов map позволяют аккуратно объединить преобразования без вложенных проверок на null.
import java.util.Optional;
record Address(String city, String country) {}
record User(String name, Address address) {}
User user = new User("Alice", new Address("New York", "US"));
// Chain of maps — safe even if any step returns null
Optional<String> city = Optional.ofNullable(user)
.map(User::address)
.map(Address::city)
.map(String::toUpperCase);
System.out.println(city.orElse("Unknown")); // NEW YORKflatMap: предотвращение Optional<Optional>
Если функция преобразования возвращает Optional, используйте flatMap, чтобы сохранить плоский Optional, а не получить вложенный Optional<Optional>.
import java.util.Optional;
record User(String name, Optional<String> email) {}
User u = new User("Bob", Optional.of("bob@example.com"));
// map returns Optional<Optional<String>> — ugly!
Optional<Optional<String>> nested = Optional.of(u).map(User::email);
// flatMap flattens: Optional<String>
Optional<String> flat = Optional.of(u).flatMap(User::email);
System.out.println(flat.get()); // bob@example.comПрактический flatMap: цепочка репозитория
Распространённый шаблон: объединение поисковых запросов к репозиторию, каждый из которых возвращает Optional.
import java.util.Optional;
record Order(long id, long userId, double total) {}
record User(long id, String name, String email) {}
static Optional<Order> findOrder(long orderId) {
return orderId == 1 ? Optional.of(new Order(1, 42, 99.99)) : Optional.empty();
}
static Optional<User> findUser(long userId) {
return userId == 42 ? Optional.of(new User(42, "Alice", "a@co.com")) : Optional.empty();
}
Optional<String> email = findOrder(1)
.flatMap(o -> findUser(o.userId()))
.map(User::email);
System.out.println(email.orElse("Not found")); // a@co.comКратко о map и flatMap
Выбор между map и flatMap:
- Используйте
map, когда функция возвращает обычное значение (String, целое число) - Используйте
flatMap, когда функция возвращает Optional
import java.util.Optional;
// map: T → R
Optional.of("hello").map(String::toUpperCase); // Optional<String>
Optional.of("42").map(Integer::parseInt); // Optional<Integer>
// flatMap: T → Optional<R>
Optional.of("42").flatMap(s -> {
try { return Optional.of(Integer.parseInt(s)); }
catch (NumberFormatException e) { return Optional.empty(); }
}); // Optional<Integer> (not Optional<Optional<Integer>>)Безопасный разбор с flatMap
Удобный шаблон: оборачивайте рискованные операции, которые могут завершиться неудачей, в Optional с помощью flatMap.
import java.util.Optional;
static Optional<Integer> tryParseInt(String s) {
try { return Optional.of(Integer.parseInt(s)); }
catch (NumberFormatException e) { return Optional.empty(); }
}
Optional<String> input = Optional.of("123");
Optional<Integer> parsed = input.flatMap(JavaOptional::tryParseInt);
System.out.println(parsed.map(n -> n * 2).orElse(-1)); // 246
Optional<String> bad = Optional.of("abc");
Optional<Integer> failed = bad.flatMap(JavaOptional::tryParseInt);
System.out.println(failed.orElse(-1)); // -1Сбор Optional
Отфильтруйте и преобразуйте поток Optional, чтобы собрать только присутствующие значения.
import java.util.*;
import java.util.stream.*;
List<String> inputs = List.of("10", "abc", "25", "nope", "7");
List<Integer> numbers = inputs.stream()
.flatMap(s -> {
try { return Stream.of(Integer.parseInt(s)); }
catch (NumberFormatException e) { return Stream.empty(); }
})
.collect(Collectors.toList());
System.out.println(numbers); // [10, 25, 7]Обход вложенных объектов
map и flatMap делают безопасным обход глубокого графа объектов без утомительных проверок на null.
import java.util.Optional;
record Company(String name, Optional<Address> address) {}
record Address(String city, Optional<String> zipCode) {}
Company company = new Company(
"Acme Corp",
Optional.of(new Address("Austin", Optional.of("78701")))
);
String zip = Optional.of(company)
.flatMap(Company::address)
.flatMap(Address::zipCode)
.orElse("N/A");
System.out.println(zip); // 78701map для преобразования типа
map отлично подходит для преобразования типов значений Optional — например, для разбора, форматирования или извлечения вложенного поля.
import java.util.Optional;
import java.time.LocalDate;
// Parse a date from String safely
static Optional<LocalDate> parseDate(String s) {
try { return Optional.of(LocalDate.parse(s)); }
catch (Exception e) { return Optional.empty(); }
}
Optional<String> dateStr = Optional.of("2024-06-15");
Optional<Integer> year = dateStr
.flatMap(JavaOptional::parseDate)
.map(LocalDate::getYear);
System.out.println(year.orElse(-1)); // 2024Построение конвейеров
Сочетание filter, map и flatMap позволяет создавать выразительные конвейеры обработки данных, которые на каждом шаге обрабатывают отсутствие значения без явных проверок на null.
import java.util.Optional;
record Product(String sku, String category, boolean inStock, double price) {}
static Optional<Product> findProduct(String sku) {
if ("LAPTOP-1".equals(sku))
return Optional.of(new Product("LAPTOP-1", "Electronics", true, 999.99));
return Optional.empty();
}
Optional<String> priceTag = findProduct("LAPTOP-1")
.filter(Product::inStock)
.filter(p -> p.price() < 2000)
.map(p -> String.format("%s: $%.2f", p.sku(), p.price()));
System.out.println(priceTag.orElse("Not available")); // LAPTOP-1: $999.99Быстрая проверка
Когда для Optional следует использовать flatMap вместо map?
Итоги: преобразование Optional: map и flatMap
Главные выводы:
- map() преобразует присутствующее значение и оборачивает результат в Optional
- flatMap() предназначен для функций, возвращающих Optional, и предотвращает появление вложенных Optional
- Объединяйте несколько вызовов map для многоэтапных преобразований без проверок на null
- Используйте flatMap для объединения вызовов репозитория или сервиса, возвращающих Optional
- Сочетайте filter + map + flatMap, чтобы создавать безопасные для null конвейеры обработки данных
- Используйте flatMap Optional::stream, чтобы включать Optional в конвейеры потоков
Часто задаваемые вопросы
Урок «Преобразование Optional: map и flatMap» бесплатный?
Да — полный текст урока «Преобразование Optional: map и flatMap» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.
Чему я научусь в уроке «Преобразование Optional: map и flatMap»?
Объединяйте преобразования в цепочки с помощью map и flatMap, обрабатывая значения без проверок на null. Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Java Academy?
Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Преобразование Optional: map и flatMap»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Java Academy?
Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Зачем нужен Optional
- Создание и проверка Optional
- Преобразование Optional: map и flatMap
- Рекомендации по использованию Optional