0Pricing
Java Academy · 课时

转换 Optional:map 与 flatMap

使用 map 和 flatMap 链式转换值,无需进行 null 检查

转换 Optional:map 与 flatMap 是 CoddyKit 上的免费 Java Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。

转换 Optional:map 与 flatMap

如果 Optional 中存在值,map() 会转换该值。flatMap() 用于在转换本身返回 Optional 时避免嵌套 Optional。

map:转换值

map(Function) 在值存在时对其应用函数,并将结果包装到新的 Optional 中。如果原 Optional 为空,则返回 empty。

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 调用可以简洁地串联转换过程,无需嵌套的空值检查。

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 之间进行选择:

  • 当函数返回普通值(String、整数)时使用 map
  • 当函数返回 Optional 时使用 flatMap
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 可以安全地遍历深层对象图,无需编写繁琐的空值检查。

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,可以构建表达力强的数据流水线,在每一步处理值缺失情况,而无需显式进行空值检查。

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,在无需空值检查的情况下完成多步转换
  • 使用 flatMap 串联返回 Optional 的仓储/服务调用
  • 组合 filter + map + flatMap,构建空值安全的数据流水线
  • 使用 flatMap Optional::stream,将 Optional 纳入流流水线

常见问题解答

「转换 Optional:map 与 flatMap」课时是免费的吗?

是的 — 「转换 Optional:map 与 flatMap」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。

「转换 Optional:map 与 flatMap」这节课中我会学到什么?

使用 map 和 flatMap 链式转换值,无需进行 null 检查 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Java Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 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