0Pricing
Java Academy · Урок

Преобразование 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 YORK

flatMap: предотвращение 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); // 78701

map для преобразования типа

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 — локальная установка не требуется.

Все уроки этого курса

  1. Зачем нужен Optional
  2. Создание и проверка Optional
  3. Преобразование Optional: map и flatMap
  4. Рекомендации по использованию Optional
← Назад к Java Academy