ZetCode

Java Stream flatMap

最后修改于 2025 年 5 月 24 日

本文演示了如何使用 Java Stream flatMap 方法来转换和扁平化嵌套数据结构。

flatMap 是一个中间流操作,它将每个元素映射到一个流,然后将这些流扁平化成一个单一的流。 它在处理嵌套集合或当每个元素可以被转换成多个元素时特别有用。

基本的 flatMap 语法

flatMap 的方法签名是

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)

mapper 函数接受一个 T 类型的元素并返回一个 R 类型的 Stream。flatMap 操作然后将所有这些流连接成一个单一的流。

扁平化嵌套集合

flatMap 最常见的用例是扁平化嵌套集合。

Main.java
void main() {

    List<List<String>> nestedLists = List.of(
        List.of("a", "b", "c"),
        List.of("d", "e"),
        List.of("f", "g", "h", "i")
    );
    
    Stream<String> flattened = nestedLists.stream()
        .flatMap(List::stream);
    
    flattened.forEach(System.out::println);
}

这个例子接受一个列表的列表,并将其扁平化成一个单一的字符串流。List::stream 方法引用将每个内部列表转换为一个流,而 flatMap 将它们组合起来。

转换和扁平化

flatMap 可以同时转换元素和扁平化结果。

Main.java
void main() {

    List<Integer> numbers = List.of(1, 2, 3, 4);
    
    Stream<Integer> doubled = numbers.stream()
        .flatMap(n -> Stream.of(n, n * 2));
    
    doubled.forEach(System.out::println);
}

这个例子接受每个数字,并将其映射到一个包含原始数字及其两倍的流。flatMap 操作然后将所有这些流组合起来。

处理可选值

flatMap 对于过滤掉空的 Optional 值同时提取值非常有用。

Main.java
void main() {

    List<Optional<String>> options = List.of(
        
        Optional.of("apple"),
        Optional.empty(),
        Optional.of("banana"),
        Optional.empty(),
        Optional.of("cherry")
    );
    
    Stream<String> fruits = options.stream()
        .flatMap(Optional::stream);
    
    fruits.forEach(System.out::println);
}

这个例子过滤掉空的 Optionals,并从存在的 Optionals 中提取值。Optional::stream 方法将一个 Optional 转换成一个包含 0 或 1 个元素的流。

将字符串拆分成单词

flatMap 可以用来分割字符串,并将所有单词组合成一个流。

Main.java
void main() {

    List<String> sentences = List.of(
        "Hello there",
        "Java streams are powerful",
        "flatMap is useful"
    );
    
    Stream<String> words = sentences.stream()
        .flatMap(s -> Stream.of(s.split(" ")));
    
    words.forEach(System.out::println);
}

这个例子将每个句子分割成单词,并将所有句子中的所有单词组合成一个单一的流。lambda 表达式通过空格分割每个字符串。

$ java Main.java
Hello
there
Java
streams
are
powerful
flatMap
is
useful

使用嵌套对象

flatMap 对于从对象中提取嵌套集合非常有用。

Main.java
record Order(String id, List<String> items) {
}

void main() {

    List<Order> orders = List.of(
        new Order("001", List.of("Shirt", "Pants")),
        new Order("002", List.of("Shoes", "Socks", "Hat")),
        new Order("003", List.of("Jacket"))
    );
    
    Stream<String> allItems = orders.stream()
        .flatMap(order -> order.items().stream());
    
    allItems.forEach(System.out::println);
}

这个例子将所有订单中的所有商品提取到一个单一的流中。每个订单的商品都被转换成一个流,然后被扁平化。

$ java Main.java
Shirt
Pants
Shoes
Socks
Hat
Jacket

组合多个流

flatpMap 可以组合来自多个源的元素。

Main.java
void main() {

    Stream<String> stream1 = Stream.of("A", "B", "C");
    Stream<String> stream2 = Stream.of("X", "Y", "Z");
    
    Stream<Stream<String>> nested = Stream.of(stream1, stream2);
    
    Stream<String> combined = nested.flatMap(s -> s);
    
    combined.forEach(System.out::println);
}

这个例子使用 flatMap 将两个独立的流组合成一个。请注意,一旦一个流被消耗(例如在 flatMap 操作中),它就不能被重用。

生成笛卡尔积

flatMap 可以用来生成集合的笛卡尔积。

Main.java
void main() {

    List<String> colors = List.of("Red", "Green", "Blue");
    List<String> sizes = List.of("S", "M", "L");
    
    Stream<String> products = colors.stream()
        .flatMap(color -> 
            sizes.stream()
                .map(size -> color + " " + size)
        );
    
    products.forEach(System.out::println);
}

这个例子生成颜色和大小的所有可能组合。每种颜色都使用嵌套的 flatMap 和 map 操作与每个大小配对。

$ java Main.java
Red S
Red M
Red L
Green S
Green M
Green L
Blue S
Blue M
Blue L

flatMap 与原始类型流

你可以使用 flatMapToIntflatMapToDoubleflatMapToLong 来扁平化和处理数字集合。例如,你可以将表示数字的字符串列表转换成一个单一的 IntStream

Main.java
void main() {

    List<String> numberStrings = List.of("1,2,3", "4,5", "6");

    IntStream numbers = numberStrings.stream()
        .flatMapToInt(s -> Arrays.stream(s.split(",")).mapToInt(Integer::parseInt));

    numbers.forEach(System.out::println);
}

这个例子通过逗号分割每个字符串,解析数字,并将它们扁平化成一个单一的 IntStream

解析和扁平化 CSV 数据

flatMap 可以用来解析 CSV 字符串列表,提取字段,并将它们扁平化成一个单一的流以进行进一步处理。

Main.java
record User(String firstName, String lastName, int age) {
}

void main() {

    List<String> csvRows = List.of("John,Doe,30", "Jane,Smith,25",
            "Bob,Johnson,40");

    Stream<User> users = csvRows.stream()
            .map(row -> row.split(",")) // Split row
            .map(fields -> new User(fields[0], fields[1], 
                Integer.parseInt(fields[2]))); // Convert to User

    users.forEach(System.out::println);
}

该示例演示了如何解析 CSV 数据,其中每一行被拆分为字段,然后每个字段用于创建一个 User 对象。这里没有显式使用 flatMap 方法,但是如果你想扁平化嵌套结构或组合多个用户流,可以应用它。

来源

Java Stream flatMap 文档

在本文中,我们探讨了 Java Stream flatMap 方法。它是一个强大的工具,用于处理嵌套数据结构,组合多个流,并在扁平化结果的同时转换元素。

作者

我的名字是 Jan Bodnar,我是一位充满热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。 至今,我已撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有Java教程