Bootstrap

Java Stream流,史上最全,玩转Stream流,过滤,分组,规约,集合等

Stream流的介绍

在 Java 中,Stream 流是 Java 8 引入的一个新的抽象层,用于对集合(如 List、Set 等)中的元素进行一系列的操作,提供了一种高效且易于表达的方式来处理数据序列。下面从多个方面详细介绍 Stream 流。

特点

  • 声明式编程:传统的集合操作通常是命令式的,需要手动编写循环来遍历集合并处理元素;而 Stream 流采用声明式编程风格,只需描述想要执行的操作,而无需关心具体的实现细节。
  • 惰性求值:Stream 操作分为中间操作和终端操作。中间操作只是定义了一个操作流程,并不会立即执行,只有在遇到终端操作时才会触发整个流的处理过程。
  • 可并行处理:Stream 流可以很方便地进行并行计算,利用多核处理器的优势提高处理效率。

创建 Stream 流的方式

从集合创建

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamCreationExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        // 从集合创建 Stream
        Stream<Integer> stream = numbers.stream();
    }
}

从数组创建

import java.util.stream.Stream;

public class StreamCreationFromArray {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3, 4, 5};
        // 从数组创建 Stream
        Stream<Integer> stream = Arrays.stream(numbers).boxed();
    }
}

使用 Stream.of()方法创建

import java.util.stream.Stream;

public class StreamCreationUsingOf {
    public static void main(String[] args) {
        // 使用 Stream.of() 方法创建 Stream
        Stream<String> stream = Stream.of("apple", "banana", "cherry");
    }
}

惰性求值

public class StreamDemo {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");

        // 按照字符串长度排序
        List<String> collect = names.stream()
                .peek(x -> System.out.println("过滤前数据:" + x))
                .filter(name -> name.length() > 3)
                .peek(x -> System.out.println("过滤后数据:" + x))
                .collect(Collectors.toList());
        System.out.println(collect);
    }
}

Stream是惰性求值,中间操作只是定义了一个操作流程,并不会立即执行,只有在遇到终端操作时才会触发整个流的处理过程,所以上面的代码不是全部数据先执行过滤前逻辑,再执行过滤后逻辑,而是一个元素接着一个元素,先执行过滤前的逻辑,接着执行过滤,再执行过滤后的逻辑。

输出的数据如下:

第一个元素
过滤前数据:Alice
过滤后数据:Alice

第二个元素
过滤前数据:Bob

第三个元素
过滤前数据:Charlie
过滤后数据:Charlie

第四个元素
过滤前数据:David
过滤后数据:David

第五个元素
过滤前数据:Eve
[Alice, Charlie, David]

表格

流类型方法名作用方法类型
遍历forEach遍历数据终端
forEachOrdered按照流的顺序遍历数据,一般在并行流中使用终端
peek在流中执行一些操作,不改变流的数据中间
匹配findFirst返回流中的第一个元素终端
findAny返回流中的任意一个元素。顺序流通常放回第一个元素,并行流放
回任意元素,对此运行结果相同,和线程调度和 JVM 优化有关
终端
anyMatch判断流中是否存在至少一个元素满足给定的条件终端
allMatch判断流中的所有元素是否都满足给定的条件终端
noneMatch判断流中是否不存在任何元素满足给定的条件终端
映射map将流中的元素转换成任意类型的对象中间
mapToInt返回对应的基本类型流中间
mapToLong返回对应的基本类型流中间
mapToDouble返回对应的基本类型流中间
flatMap将流中的每个元素通过给定的函数转换为一个流,然后将所有这
些流合并成一个单一的流,于将嵌套的集合或数组展平成一个单一的流
中间
flatMapToInt但将流中的每个元素转换为一个基本类型的流中间
flatMapToLong但将流中的每个元素转换为一个基本类型的流中间
flatMapToDouble但将流中的每个元素转换为一个基本类型的流中间
mapMulti是一个通用的转换方法,允许将流中的每个元素映射为 零个、
一个或多个新的元素
中间
mapMultiToInt专门用于将流中的元素映射为零个、一个或多个int 类型的值中间
mapMultiToLong专门用于将流中的元素映射为零个、一个或多个long 类型的值中间
mapMultiToDouble专门用于将流中的元素映射为零个、一个或多个 double 类型的值中间
过滤filter方法用于筛选流中的元素中间
去重distinct用于对数据进行去重中间
排序sorted用于对数据进行排序中间
限制limit方法用于限制流中元素的数量,返回前n个元素中间
跳过skip方法用于跳过流中的前 n 个元素,返回剩余的元素中间
归约reduce方法用于对流中的元素进行聚合操作,将流中的元素组合成一
个单一的结果,计算流中元素的总和、乘积、最大值、最小值等
中间
聚合min方法用于查找流中的最小值中间
max方法用于查找流中的最大值中间
count方法用于统计流中元素的数量中间
合并concat方法用于将两个流合并为一个流中间
收集collect就是把一个流收集起来,最终可以是收集成一个值也可以
收集成一个新的集合
终端
toList将流中的元素收集到一个 List 中终端
toSet将流中的元素收集到一个 Set 中终端
toMap将流中的元素收集到一个 Map中终端
summingInt计算整数属性的总和终端
averagingInt计算整数属性的总和终端
maxBy查找最大值终端
minBy查找最小值终端
groupingBy根据某个属性对元素进行分组终端
partitioningBy根据某个布尔条件将元素分为两组终端
joining将流中的字符串元素连接成一个单一的字符终端

遍历

forEach

介绍

forEach 方法用于对流中的每个元素执行指定的操作。这是一个终端操作,意味着在调用 forEach 后,流的处理将结束,不会再进行后续的操作。

特点

  • 无序遍历:对于并行流,forEach 不保证元素的处理顺序,可能会打乱原始顺序。
  • 适用于不需要保持元素顺序的场景。

示例代码

import java.util.Arrays;
import java.util.List;

public class ForEachExample {
    public static void main(String[] args) {
        List<String> word = Arrays.asList("A", "B", "C", "D");
        // 使用 forEach 遍历并打印
        word.stream().forEach(word -> System.out.println(word)); // 顺序流顺序固定,ABCD
        word.parallelStream().forEach(word -> System.out.println(word)); // 并行流循序不固定,CDBA
    }
}

forEachOrdered

介绍

forEachOrdered 方法也用于对流中的每个元素执行指定的操作,但与 forEach 不同的是,forEachOrdered 保证按照流的顺序依次处理每个元素。这在并行流中尤其有用,可以确保元素按原始顺序被处理。

特点

  • 有序遍历:即使在并行流中,也会按照流的顺序依次处理元素。
  • 适用于需要保持元素顺序的场景。

示例代码

import java.util.Arrays;
import java.util.List;

public class ForEachOrderedExample {
    public static void main(String[] args) {
        List<String> word = Arrays.asList("A", "B", "C", "D");
        // 使用 forEachOrdered 遍历并打印,保证顺序
        word.parallelStream().forEachOrdered(word -> System.out.println(word));
    }
}

peek

介绍

peek 方法用于在流的每个元素上执行某些操作,但 不会改变流中的数据。它主要用于调试或记录日志等场景,因为它允许你在流的中间操作中插入副作用操作(如打印),而不影响流的处理。

特点

  • 中间操作:peek 是一个中间操作,必须与其他终端操作(如 collectforEach 等)结合使用。r如果只使用peek不使用终端操作,在里面做的打印等操作不会生效。
  • 无副作用:理想情况下,peek 中的操作不应改变流的状态或外部状态,但在实际使用中,可以用于记录日志等副作用操作。

示例代码

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class PeekExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("A", "BBB", "CC", "DDDD");
        // 使用 peek 打印每个word,并收集到新的列表中
        List<String> wordList = words.stream()
                .peek(word -> System.out.println("处理字母: " + word))
                .filter(word -> word.length() > 2)
                .peek(word -> System.out.println("过滤后字母: " + word))
                .collect(Collectors.toList());
        System.out.println("结果列表: " + wordList);
        
    }
}

打印结果如下:

处理字母: A
处理字母: BBB
过滤后字母: BBB
处理字母: CC
处理字母: DDDD
过滤后字母: DDDD
结果列表: [BBB, DDDD]

为什么是这个顺序

Stream 的执行模型

Java Stream 是惰性求值(lazy evaluation)的,这意味着流中的操作不会立即执行,而是等到终端操作(如 collectforEach 等)被调用时才会触发整个流水线的执行。在流水线中,中间操作(如 peekfiltermap 等)只是定义了流水线的步骤,而不会立即处理数据。触发整个流水线的执行,每一个元素以此走完流水在接着下一个。

虽然 peek 是分开定义的,但它们实际上是在同一个流水线中依次执行的。当终端操作 collect 被调用时,整个流水线会按顺序处理每个元素。因此,对于每个元素,以下步骤会依次执行:

处理字母: A

第一个 peek 打印 “处理字母: A”。
filter 判断 “A” 的长度不大于 2,过滤掉。

处理字母: BBB

第一个 peek 打印 “处理字母: BBB”。
filter 判断 “BBB” 的长度大于 2,保留。
第二个 peek 打印 “过滤后字母: BBB”。

处理字母: CC

第一个 peek 打印 “处理字母: CC”。
filter 判断 “CC” 的长度不大于 2,过滤掉。

处理字母: DDDD

第一个 peek 打印 “处理字母: DDDD”。
filter 判断 “DDDD” 的长度大于 2,保留。
第二个 peek 打印 “过滤后字母: DDDD”。

因此,输出顺序如下:

处理字母: A
处理字母: BBB
过滤后字母: BBB
处理字母: CC
处理字母: DDDD
过滤后字母: DDDD
结果列表: [BBB, DDDD]

匹配

findFirst

介绍

findFirst方法用于返回流中的第一个元素。这是一个终端操作,调用后流将不再可用。

代码示例

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class FindFirstExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);

        Optional<Integer> firstElement = numbers.stream().findFirst();

        firstElement.ifPresent(System.out::println); // 输出: 10
    }
}

findAny

介绍

findAny方法用于返回流中的任意一个元素。对于顺序流,通常返回第一个元素;对于并行流,可能返回任意一个元素(但是同一个集合多次调用放回的是同一个元素)。这是一个终端操作,调用后流将不再可用。

代码示例

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class FindAnyExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);

        Optional<Integer> anyElement = numbers.stream().findAny();

        anyElement.ifPresent(System.out::println); // 输出: 10(顺序流)

        // 并行流示例
        Optional<Integer> anyElementParallel = numbers.parallelStream().findAny();

        anyElementParallel.ifPresent(System.out::println); // 输出: 可能是任意一个元素
    }
}

anyMatch

介绍

anyMatch 方法用于判断流中是否存在至少一个元素满足给定的条件。这是一个终端操作,返回一个布尔值。

示例代码

import java.util.Arrays;
import java.util.List;

public class AnyMatchExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);

        // 判断是否存在大于 45 的元素
        boolean exists = numbers.stream().anyMatch(n -> n > 45);

        System.out.println(exists); // 输出: true
    }
}

allMatch

介绍

allMatch 方法用于判断流中的所有元素是否都满足给定的条件。这是一个终端操作,返回一个布尔值。

示例代码

import java.util.Arrays;
import java.util.List;

public class AllMatchExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);

        // 判断是否所有元素都大于 5
        boolean allGreaterThanFive = numbers.stream().allMatch(n -> n > 5);

        System.out.println(allGreaterThanFive); // 输出: true

        // 判断是否所有元素都大于 45
        boolean allGreaterThanFortyFive = numbers.stream().allMatch(n -> n > 45);

        System.out.println(allGreaterThanFortyFive); // 输出: false
    }
}

noneMatch

介绍

noneMatch 方法用于判断流中是否不存在任何元素满足给定的条件。这是一个终端操作,返回一个布尔值。

示例代码

import java.util.Arrays;
import java.util.List;

public class NoneMatchExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);

        // 判断是否不存在大于 60 的元素
        boolean noneGreaterThanSixty = numbers.stream().noneMatch(n -> n > 60);

        System.out.println(noneGreaterThanSixty); // 输出: true

        // 判断是否不存在大于 45 的元素
        boolean noneGreaterThanFortyFive = numbers.stream().noneMatch(n -> n > 45);

        System.out.println(noneGreaterThanFortyFive); // 输出: false
    }
}

映射

map

介绍

map 方法用于将流中的每个元素转换为另一种类型的对象。它接受一个 Function 作为参数,这个函数会被应用到流中的每个元素上,生成一个新的流。

特点

  • 中间操作:map 返回一个新的流,允许后续的链式操作。
  • 灵活性高:可以将元素转换为任意类型的对象。

示例代码

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class MapExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "cherry");

        // 将每个单词转换为大写
        List<String> upperCaseWords = words.stream()
                .map(String::toUpperCase)
                .collect(Collectors.toList());

        System.out.println(upperCaseWords); // 输出: [APPLE, BANANA, CHERRY]

        // 将每个单词转换为它的长度
        List<Integer> wordLengths = words.stream()
                .map(String::length)
                .collect(Collectors.toList());

        System.out.println(wordLengths); // 输出: [5, 6, 6]
    }
}

mapToInt

介绍

mapToInt 方法用于将流中的元素转换为 int 类型的基本类型流 (IntStream)。它接受一个 ToIntFunction 作为参数,这个函数会被应用到流中的每个元素上,生成一个 IntStream

特点

  • 中间操作:mapToInt 返回一个 IntStream,允许后续针对基本类型 int 的操作。
  • 性能优化:相比于通用的 mapmapToInt 可以减少装箱(boxing)操作,提高性能。

示例代码

import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;

public class MapToIntExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "cherry");

        // 将每个单词转换为它的长度,并计算总长度
        int totalLength = words.stream()
                .mapToInt(String::length)
                .sum();

        System.out.println(totalLength); // 输出: 17

        // 过滤长度大于5的单词,并打印它们的长度
        words.stream()
                .mapToInt(String::length)
                .filter(length -> length > 5)
                .forEach(System.out::println); // 输出: 6, 6
    }
}

mapToLong

介绍

mapToLong 方法用于将流中的元素转换为 long 类型的基本类型流 (LongStream)。它接受一个 ToLongFunction 作为参数,这个函数会被应用到流中的每个元素上,生成一个 LongStream

特点

  • 中间操作:mapToLong 返回一个 LongStream,允许后续针对基本类型 long 的操作。
  • 性能优化:类似于 mapToIntmapToLong 可以减少装箱操作,提高性能。

示例代码

import java.util.Arrays;
import java.util.List;
import java.util.stream.LongStream;

public class MapToLongExample {
    public static void main(String[] args) {
        List<Long> numbers = Arrays.asList(100L, 200L, 300L, 400L);

        // 将每个数字乘以2
        LongStream doubledNumbers = numbers.stream()
                .mapToLong(n -> n * 2);

        doubledNumbers.forEach(System.out::println); // 输出: 200, 400, 600, 800

        // 计算总和
        long sum = numbers.stream()
                .mapToLong(Long::longValue)
                .sum();

        System.out.println(sum); // 输出: 1000
    }
}

mapToDouble

介绍

mapToDouble 方法用于将流中的元素转换为 double 类型的基本类型流 (DoubleStream)。它接受一个 ToDoubleFunction 作为参数,这个函数会被应用到流中的每个元素上,生成一个 DoubleStream

特点

  • 中间操作:mapToDouble 返回一个 DoubleStream,允许后续针对基本类型 double 的操作。
  • 性能优化:类似于 mapToIntmapToLongmapToDouble 可以减少装箱操作,提高性能。

示例代码

import java.util.Arrays;
import java.util.List;
import java.util.stream.DoubleStream;

public class MapToDoubleExample {
    public static void main(String[] args) {
        List<Double> numbers = Arrays.asList(1.5, 2.5, 3.5, 4.5);

        // 将每个数字乘以2
        DoubleStream doubledNumbers = numbers.stream()
                .mapToDouble(n -> n * 2);

        doubledNumbers.forEach(System.out::println); // 输出: 3.0, 5.0, 7.0, 9.0

        // 计算平均值
        double average = numbers.stream()
                .mapToDouble(Double::doubleValue)
                .average()
                .orElse(0.0);

        System.out.println(average); // 输出: 3.0
    }
}

flatMap

介绍

flatMap 方法将流中的每个元素通过给定的函数转换为一个流,然后将所有这些流合并成一个单一的流。它主要用于将嵌套的集合或数组展平成一个单一的流。

特点

  • 中间操作:flatMap 返回一个新的流,允许后续的链式操作。
  • 灵活性高:可以将元素转换为任意类型的流。

示例代码

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class FlatMapExample {
    public static void main(String[] args) {
        List<List<Integer>> listOfLists = Arrays.asList(
            Arrays.asList(1, 2, 3),
            Arrays.asList(4, 5),
            Arrays.asList(6, 7, 8, 9)
        );

        // 使用 flatMap 将嵌套的列表展平成一个单一的流
        List<Integer> flatList = listOfLists.stream()
            .flatMap(List::stream)
            .collect(Collectors.toList());

        System.out.println(flatList); // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]
    }
}

flatMapToInt

介绍

flatMapToInt 方法将流中的每个元素转换为一个 int 类型的基本类型流 (IntStream),然后将所有这些流合并成一个单一的 IntStream

特点

  • 中间操作:flatMapToInt 返回一个 IntStream,允许后续针对基本类型 int 的操作。
  • 性能优化:相比于通用的 flatMapflatMapToInt 可以减少装箱(boxing)操作,提高性能。

示例代码

import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;

public class FlatMapToIntExample {
    public static void main(String[] args) {
        List<int[]> arrayOfArrays = Arrays.asList(
            new int[]{1, 2, 3},
            new int[]{4, 5},
            new int[]{6, 7, 8, 9}
        );

        // 使用 flatMapToInt 将嵌套的数组展平成一个单一的 IntStream
        int sum = arrayOfArrays.stream()
            .flatMapToInt(Arrays::stream)
            .sum();

        System.out.println(sum); // 输出: 45
    }
}

flatMapToLong

介绍

flatMapToLong 方法将流中的每个元素转换为一个 long 类型的基本类型流 (LongStream),然后将所有这些流合并成一个单一的 LongStream

特点

  • 中间操作:flatMapToLong 返回一个 LongStream,允许后续针对基本类型 long 的操作。
  • 性能优化:类似于 flatMapToIntflatMapToLong 可以减少装箱操作,提高性能。

示例代码

import java.util.Arrays;
import java.util.List;
import java.util.stream.LongStream;

public class FlatMapToLongExample {
    public static void main(String[] args) {
        List<long[]> arrayOfArrays = Arrays.asList(
            new long[]{1L, 2L, 3L},
            new long[]{4L, 5L},
            new long[]{6L, 7L, 8L, 9L}
        );

        // 使用 flatMapToLong 将嵌套的数组展平成一个单一的 LongStream
        long sum = arrayOfArrays.stream()
            .flatMapToLong(Arrays::stream)
            .sum();

        System.out.println(sum); // 输出: 45
    }
}

flatMapToDouble

介绍

flatMapToDouble 方法将流中的每个元素转换为一个 double 类型的基本类型流 (DoubleStream),然后将所有这些流合并成一个单一的 DoubleStream

特点

  • 中间操作:flatMapToDouble 返回一个 DoubleStream,允许后续针对基本类型 double 的操作。
  • 性能优化:类似于 flatMapToIntflatMapToLongflatMapToDouble 可以减少装箱操作,提高性能。

示例代码

import java.util.Arrays;
import java.util.List;
import java.util.stream.DoubleStream;

public class FlatMapToDoubleExample {
    public static void main(String[] args) {
        List<double[]> arrayOfArrays = Arrays.asList(
            new double[]{1.1, 2.2, 3.3},
            new double[]{4.4, 5.5},
            new double[]{6.6, 7.7, 8.8, 9.9}
        );

        // 使用 flatMapToDouble 将嵌套的数组展平成一个单一的 DoubleStream
        double sum = arrayOfArrays.stream()
            .flatMapToDouble(Arrays::stream)
            .sum();

        System.out.println(sum); // 输出: 49.5
    }
}

mapMulti

介绍

mapMulti 是一个中间操作,允许将流中的每个元素映射为零个、一个或多个新的元素。它接受一个 BiConsumer,该消费者接收当前元素和一个 Consumer,通过这个 Consumer 可以发射任意数量的新元素。

特点

  • 灵活性高:可以生成零个、一个或多个新元素。
  • 中间操作:返回一个新的流,允许后续的链式操作。

示例代码

假设我们有一个包含多个标签的字符串列表,我们希望将每个字符串拆分为单独的标签并生成新的流元素。

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class MapMultiExample {
    public static void main(String[] args) {
        List<String> items = Arrays.asList("apple,banana", "orange", "grape,melon,kiwi");

        List<String> tags = items.stream()
            .flatMap(item -> Arrays.stream(item.split(","))) // 使用 flatMap 也可以实现类似功能
            //.mapMulti((item, consumer) -> {
            //    for (String tag : item.split(",")) {
            //        consumer.accept(tag);
            //    }
            //})
            .collect(Collectors.toList());

        // 使用 mapMulti 的方式
        List<String> tagsMapMulti = items.stream()
            .mapMulti((item, consumer) -> {
                for (String tag : item.split(",")) {
                    consumer.accept(tag);
                }
            })
            .collect(Collectors.toList());

        System.out.println(tagsMapMulti); // 输出: [apple, banana, orange, grape, melon, kiwi]
    }
}

注意:在上面的示例中,flatMap 已经可以实现类似的功能,但 mapMulti 提供了一种更直观的方式来生成多个输出元素。

mapMultiToInt

介绍

mapMultiToInt 是一个中间操作,专门用于将流中的元素映射为零个、一个或多个 int 类型的值。它返回一个 IntStream,适用于需要处理基本类型 int 的场景。

特点

  • 中间操作:返回一个 IntStream,允许后续针对基本类型 int 的操作。
  • 性能优化:避免了装箱操作,提高了性能。

示例代码

假设我们有一个包含多个整数的列表,每个整数可能对应多个因子,我们希望将这些因子展平成一个单一的 IntStream

import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;

public class MapMultiToIntExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(12, 15, 20);

        IntStream factors = numbers.stream()
            .mapMultiToInt(number -> {
                for (int i = 1; i <= number; i++) {
                    if (number % i == 0) {
                        // 发射因子
                        // 注意:实际实现需要避免发射超过number的因子
                        if (i != number / i || number % i == 0) { // 简化条件
                            // 正确的方式是只发射i和number/i中的一个,避免重复
                            if (i <= number / i) {
                                // 发射因子i
                                // 这里只是示例,实际需要更复杂的逻辑来避免重复
                                // 例如,只发射i如果i*i != number
                                if (i * i != number) {
                                    // 发射i和number/i
                                    // 但mapMultiToInt不允许发射多个值,需要调整
                                }
                                // 由于mapMultiToInt每次只能发射一个值,需要分开处理
                                // 更好的方式是使用 flatMap 或其他方法
                            }
                        }
                    }
                // 正确的实现应使用多个consumer.accept调用
                // 但由于mapMultiToInt的限制,这里只是一个简化示例
                // 实际应用中建议使用 flatMap 或其他适合的方法
            });

        // 由于mapMultiToInt的限制,上面的示例并不适用
        // 更合适的示例应使用 flatMap 或其他方法

        // 正确的示例使用 flatMapToInt
        IntStream correctFactors = numbers.stream()
            .flatMapToInt(number -> {
                IntStream factorsStream = IntStream.empty();
                for (int i = 1; i <= number; i++) {
                    if (number % i == 0) {
                        factorsStream = factorsStream.concat(IntStream.of(i));
                        if (i != number / i) { // 避免平方因子重复
                            factorsStream = factorsStream.concat(IntStream.of(number / i));
                        }
                    }
                }
                return factorsStream;
            });

        // 由于IntStream不支持concat,可以使用boxed或其他方式
        // 更简单的方式是分开处理
        IntStream factorsStream = numbers.stream()
            .flatMapToInt(number -> IntStream.rangeClosed(1, number)
                .filter(i -> number % i == 0)
            );

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

注意:mapMultiToInt 目前(截至Java 17)并不支持在一个映射中发射多个值,因此上面的示例并不适用。实际应用中,建议使用 flatMapToInt 来实现类似的功能。

正确的使用方式

由于 mapMultiToInt 不支持在一个映射中发射多个值,推荐使用 flatMapToInt 来实现将每个元素映射为多个 int 值的场景。

IntStream factors = numbers.stream()
    .flatMapToInt(number -> IntStream.rangeClosed(1, number)
        .filter(i -> number % i == 0)
    );

mapMultiToLong

介绍

mapMultiToLong 是一个中间操作,专门用于将流中的元素映射为零个、一个或多个 long 类型的值。它返回一个 LongStream,适用于需要处理基本类型 long 的场景。

特点

  • 中间操作:返回一个 LongStream,允许后续针对基本类型 long 的操作。
  • 性能优化:避免了装箱操作,提高了性能。

示例代码

类似于 mapMultiToInt,但由于 mapMultiToLong 也不支持在一个映射中发射多个值,推荐使用 flatMapToLong

import java.util.Arrays;
import java.util.List;
import java.util.stream.LongStream;

public class MapMultiToLongExample {
    public static void main(String[] args) {
        List<Long> numbers = Arrays.asList(12L, 15L, 20L);

        // 使用 flatMapToLong 来发射多个 long 值
        LongStream factors = numbers.stream()
            .flatMapToLong(number -> LongStream.rangeClosed(1, number)
                .filter(i -> number % i == 0)
            );

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

mapMultiToDouble

介绍

mapMultiToDouble 是一个中间操作,专门用于将流中的元素映射为零个、一个或多个 double 类型的值。它返回一个 DoubleStream,适用于需要处理基本类型 double 的场景。

特点

  • 中间操作:返回一个 DoubleStream,允许后续针对基本类型 double 的操作。
  • 性能优化:避免了装箱操作,提高了性能。

示例代码

同样,mapMultiToDouble 不支持在一个映射中发射多个值,推荐使用 flatMapToDouble

import java.util.Arrays;
import java.util.List;
import java.util.stream.DoubleStream;

public class MapMultiToDoubleExample {
    public static void main(String[] args) {
        List<Double> numbers = Arrays.asList(12.0, 15.0, 20.0);

        // 使用 flatMapToDouble 来发射多个 double 值
        DoubleStream factors = numbers.stream()
            .flatMapToDouble(number -> DoubleStream.of(/* 这里需要生成多个double值,示例不适用 */));

        // 更合适的做法是使用 flatMapToDouble 结合生成器
        // 例如,发射每个数字的小数部分
        DoubleStream decimalParts = numbers.stream()
            .flatMapToDouble(number -> DoubleStream.of(number % 1.0));

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

过滤

filter

介绍

filter 方法用于筛选流中的元素,只保留满足特定条件的元素。它接受一个 Predicate 函数作为参数,该函数对流中的每个元素进行测试,返回 true 的元素将被保留。

示例代码

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class FilterExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 筛选出所有偶数
        List<Integer> evenNumbers = numbers.stream()
                                           .filter(n -> n % 2 == 0)
                                           .collect(Collectors.toList());

        System.out.println(evenNumbers); // 输出: [2, 4, 6, 8, 10]
    }
}

去重

distinct

介绍

distinct 方法用于去除流中的重复元素,返回一个包含唯一元素的新流。它依赖于元素的 equalshashCode 方法来判断元素是否相同。

示例代码

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class DistinctExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5, 6, 6, 7);

        // 去除重复元素
        List<Integer> uniqueNumbers = numbers.stream()
                                             .distinct()
                                             .collect(Collectors.toList());

        System.out.println(uniqueNumbers); // 输出: [1, 2, 3, 4, 5, 6, 7]
    }
}

排序

默认排序(自然顺序)

sorted() 方法无需参数,默认按照元素的自然顺序进行排序。这要求元素实现 Comparable 接口。

示例:对整数列表进行正序排序**

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class SortedDefaultExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 2);

        List<Integer> sortedNumbers = numbers.stream()
                                            .sorted()
                                            .collect(Collectors.toList());

        System.out.println(sortedNumbers); // 输出: [1, 2, 3, 5, 8]
    }
}

自定义排序(倒序)

你可以通过传递一个自定义的 Comparator 来实现倒序排序。使用 Comparator.reverseOrder() 可以轻松实现倒序。

示例:对字符串列表进行倒序排序

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class SortedReverseExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");

        List<String> sortedNamesDesc = names.stream()
                                           .sorted(Comparator.reverseOrder())
                                           .collect(Collectors.toList());

        System.out.println(sortedNamesDesc); // 输出: [Eve, David, Charlie, Bob, Alice]
    }
}

根据对象的某个属性排序

当处理对象流时,通常需要根据对象的某个属性进行排序。可以使用 Comparator.comparing 方法来实现。

示例:按年龄对人员列表进行正序排序

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

public class SortedByPropertyExample {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 35)
        );

        // 按年龄正序排序
        List<Person> sortedByAgeAsc = people.stream()
                                          .sorted(Comparator.comparing(Person::getAge))
                                          .collect(Collectors.toList());

        System.out.println(sortedByAgeAsc); // 输出: [Bob (25), Alice (30), Charlie (35)]
    }
}

多条件排序

有时需要根据多个属性进行排序,例如先按年龄排序,再按姓名排序。可以通过链式调用 thenComparing 方法来实现。

示例:先按年龄升序排序,再按姓名升序排序

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class MultiCriteriaSortExample {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 25),
            new Person("David", 30)
        );

        // 先按年龄升序,再按姓名升序
        List<Person> sortedByNameThenAge = people.stream()
                                               .sorted(
                                                   Comparator.comparing(Person::getAge)
                                                             .thenComparing(Person::getName)
                                               )
                                               .collect(Collectors.toList());

        System.out.println(sortedByNameThenAge);
        // 输出:
        // [Bob (25), Charlie (25), Alice (30), David (30)]
    }
}

使用特定比较器

对于基本数据类型,可以使用 Comparator.comparingIntComparator.comparingLongComparator.comparingDouble 来避免装箱操作,提高性能。

示例:按字符串长度升序排序

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class SortedByLengthExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");

        // 按字符串长度升序排序
        List<String> sortedByLength = names.stream()
                                         .sorted(Comparator.comparingInt(String::length))
                                         .collect(Collectors.toList());

        System.out.println(sortedByLength); // 输出: [Bob, Eve, Alice, David, Charlie]
    }
}

自定义比较器

如果需要更复杂的排序逻辑,可以自定义 Comparator

示例:按姓名长度降序排序,如果长度相同则按字母顺序升序

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class CustomComparatorExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve", "Anna");

        // 按姓名长度降序排序,如果长度相同则按字母顺序升序
        List<String> customSortedNames = names.stream()
                                            .sorted(
                                                Comparator.comparingInt(String::length).reversed()
                                                          .thenComparing(Comparator.naturalOrder())
                                            )
                                            .collect(Collectors.toList());

        System.out.println(customSortedNames);
        // 输出: [Charlie, Alice, David, Eve, Bob, Anna]
    }
}

在对象中使用多个排序条件

假设有一个更复杂的对象 Employee,包含 department(部门)、salary(薪水)和 name(姓名)属性。我们希望先按部门升序排序,再按薪水降序排序,最后按姓名升序排序。

示例:多级排序

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

class Employee {
    private String department;
    private double salary;
    private String name;

    public Employee(String department, double salary, String name) {
        this.department = department;
        this.salary = salary;
        this.name = name;
    }

    public String getDepartment() { return department; }
    public double getSalary() { return salary; }
    public String getName() { return name; }

    @Override
    public String toString() {
        return department + " - " + name + " ($" + salary + ")";
    }
}

public class MultiLevelSortExample {
    public static void main(String[] args) {
        List<Employee> employees = Arrays.asList(
            new Employee("Sales", 70000, "Alice"),
            new Employee("Engineering", 80000, "Bob"),
            new Employee("Sales", 75000, "Charlie"),
            new Employee("Engineering", 85000, "David"),
            new Employee("HR", 60000, "Eve")
        );

        // 先按部门升序,再按薪水降序,最后按姓名升序
        List<Employee> sortedEmployees = employees.stream()
                                                .sorted(
                                                    Comparator.comparing(Employee::getDepartment)
                                                          .thenComparing(Employee::getSalary, Comparator.reverseOrder())
                                                          .thenComparing(Employee::getName)
                                                )
                                                .collect(Collectors.toList());

        System.out.println(sortedEmployees);
        // 输出:
        // Engineering - David ($85000)
        // Engineering - Bob ($80000)
        // HR - Eve ($60000)
        // Sales - Charlie ($75000)
        // Sales - Alice ($70000)
    }
}

限制

limit

介绍

limit 方法用于限制流中元素的数量,返回前 n 个元素。它常用于分页或获取部分结果。

示例代码

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class LimitExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 获取前5个元素
        List<Integer> firstFive = numbers.stream()
                                        .limit(5)
                                        .collect(Collectors.toList());

        System.out.println(firstFive); // 输出: [1, 2, 3, 4, 5]
    }
}

跳过

skip

介绍

skip 方法用于跳过流中的前 n 个元素,返回剩余的元素。它常用于分页或忽略部分数据。

示例代码

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class SkipExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 跳过前5个元素
        List<Integer> remaining = numbers.stream()
                                       .skip(5)
                                       .collect(Collectors.toList());

        System.out.println(remaining); // 输出: [6, 7, 8, 9, 10]
    }
}

规约

reduce

介绍

reduce 方法用于对流中的元素进行聚合操作,将流中的元素组合成一个单一的结果。它可以用于计算总和、乘积、最大值、最小值等。

示例代码

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class ReduceExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 计算总和
        Optional<Integer> sum = numbers.stream()
                                      .reduce((a, b) -> a + b);
        sum.ifPresent(System.out::println); // 输出: 15

        // 计算乘积
        Optional<Integer> product = numbers.stream()
                                         .reduce((a, b) -> a * b);
        product.ifPresent(System.out::println); // 输出: 120

        // 计算最大值
        Optional<Integer> max = numbers.stream()
                                      .reduce(Integer::max);
        max.ifPresent(System.out::println); // 输出: 5

        // 计算最小值
        Optional<Integer> min = numbers.stream()
                                      .reduce(Integer::min);
        min.ifPresent(System.out::println); // 输出: 1
    }
}

聚合

min

介绍

min 方法用于查找流中的最小值。它接受一个 Comparator 函数作为参数,该函数定义了元素之间的比较规则。如果没有提供 Comparator,则默认使用元素的自然顺序。

示例代码

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;

public class MinExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 2);

        // 使用自然顺序查找最小值
        Optional<Integer> minNumber = numbers.stream()
                                             .min(Comparator.naturalOrder());

        minNumber.ifPresent(System.out::println); // 输出: 1

        // 使用自定义比较器查找最小值(例如按绝对值)
        List<Integer> withNegatives = Arrays.asList(-5, 3, -8, 1, 2);
        Optional<Integer> minAbs = withNegatives.stream()
                                                .min(Comparator.comparingInt(Math::abs));

        minAbs.ifPresent(System.out::println); // 输出: 1
    }
}

max

介绍

max 方法用于查找流中的最大值。类似于 min,它也接受一个 Comparator 函数来定义比较规则,默认使用元素的自然顺序。

示例代码

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;

public class MaxExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 2);

        // 使用自然顺序查找最大值
        Optional<Integer> maxNumber = numbers.stream()
                                             .max(Comparator.naturalOrder());

        maxNumber.ifPresent(System.out::println); // 输出: 8

        // 使用自定义比较器查找最大值(例如按绝对值)
        List<Integer> withNegatives = Arrays.asList(-5, 3, -8, 1, 2);
        Optional<Integer> maxAbs = withNegatives.stream()
                                                .max(Comparator.comparingInt(Math::abs));

        maxAbs.ifPresent(System.out::println); // 输出: -8
    }
}

count

介绍

count 方法用于统计流中元素的数量。它返回一个 long 类型的值,表示流中的元素总数。

示例代码

import java.util.Arrays;
import java.util.List;

public class CountExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");

        // 统计元素数量
        long count = names.stream()
                         .count();

        System.out.println(count); // 输出: 5
    }
}

合并

concat

介绍

concat 方法用于将两个流合并为一个流。它接受两个参数,分别是第一个流和第二个流,并返回一个新的流,其中包含两个输入流的所有元素,顺序与输入顺序一致。

示例代码

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ConcatExample {
    public static void main(String[] args) {
        List<Integer> list1 = Arrays.asList(1, 2, 3);
        List<Integer> list2 = Arrays.asList(4, 5, 6);

        // 使用 Stream.concat 合并两个流
        Stream<Integer> combinedStream = Stream.concat(list1.stream(), list2.stream());

        // 收集合并后的流到列表
        List<Integer> combinedList = combinedStream.collect(Collectors.toList());

        System.out.println(combinedList); // 输出: [1, 2, 3, 4, 5, 6]
    }
}

使用Stream.concat合并多个流

如果需要合并多个流,可以嵌套使用 Stream.concat 方法:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ConcatMultipleStreamsExample {
    public static void main(String[] args) {
        List<Integer> list1 = Arrays.asList(1, 2, 3);
        List<Integer> list2 = Arrays.asList(4, 5, 6);
        List<Integer> list3 = Arrays.asList(7, 8, 9);

        // 合并三个流
        Stream<Integer> combinedStream = Stream.concat(Stream.concat(list1.stream(), list2.stream()), list3.stream());

        // 收集合并后的流到列表
        List<Integer> combinedList = combinedStream.collect(Collectors.toList());

        System.out.println(combinedList); // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]
    }
}

使用 flatMap 合并多个流

对于合并多个流,flatMap 提供了一种更灵活的方法:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class FlatMapConcatExample {
    public static void main(String[] args) {
        List<List<Integer>> listOfLists = Arrays.asList(
            Arrays.asList(1, 2, 3),
            Arrays.asList(4, 5, 6),
            Arrays.asList(7, 8, 9)
        );

        // 使用 flatMap 合并多个列表
        List<Integer> combinedList = listOfLists.stream()
                                               .flatMap(List::stream)
                                               .collect(Collectors.toList());

        System.out.println(combinedList); // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]
    }
}

集合

collect

介绍

collect 方法用于将流中的元素累积成一个结果。它是一个终端操作,意味着在调用 collect 后,流的处理将结束,不会再进行后续的操作。collect 方法接受一个 Collector 接口的实现,该接口定义了如何将流中的元素累积成期望的结果。

示例代码

import java.util.*;
import java.util.stream.Collectors;

public class CollectExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");

        // 收集到List
        List<String> nameList = names.stream().collect(Collectors.toList());
        System.out.println(nameList); // 输出: [Alice, Bob, Charlie, David, Eve]

        // 收集到Set
        Set<String> nameSet = names.stream().collect(Collectors.toSet());
        System.out.println(nameSet); // 输出顺序可能不同: [Alice, Bob, Charlie, David, Eve]
    }
}

toList

介绍

toList 收集器将流中的元素收集到一个 List 中。这是一个便捷的方法,等同于使用 Collectors.toList()

示例代码

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> numberList = numbers.stream().collect(Collectors.toList());
System.out.println(numberList); // 输出: [1, 2, 3, 4, 5]

toSet

介绍

toSet 收集器将流中的元素收集到一个 Set 中。由于 Set 不允许重复元素,因此如果流中有重复的元素,只会保留一个。

示例代码

List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
Set<Integer> numberSet = numbers.stream().collect(Collectors.toSet());
System.out.println(numberSet); // 输出: [1, 2, 3, 4, 5]

toMap

介绍

toMap 收集器将流中的元素收集到一个 Map 中。它需要两个函数参数,分别用于生成键和值。如果存在重复的键,可以通过第三个参数指定合并策略。

示例代码

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Map<String, Integer> nameLengthMap = names.stream()
    .collect(Collectors.toMap(
        name -> name,          // 键生成函数
        String::length         // 值生成函数
    ));
System.out.println(nameLengthMap); // 输出: {Alice=5, Bob=3, Charlie=7}

// 处理重复键
List<String> duplicateNames = Arrays.asList("Alice", "Bob", "Alice");
Map<String, Integer> duplicateMap = duplicateNames.stream()
    .collect(Collectors.toMap(
        name -> name,
        String::length,
        (existing, replacement) -> existing // 保留现有值
    ));
System.out.println(duplicateMap); // 输出: {Alice=5, Bob=3}

summingInt、averagingInt、maxBy、minBy

介绍

这些收集器用于数值计算:

  • summingInt:计算整数属性的总和。
  • averagingInt:计算整数属性的平均值。
  • maxBy:查找最大值。
  • minBy:查找最小值。

这些收集器通常与对象的某个数值属性一起使用。

示例代码

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

public class NumericCollectorsExample {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 35)
        );

        // 总年龄
        int totalAge = people.stream()
                             .collect(Collectors.summingInt(Person::getAge));
        System.out.println(totalAge); // 输出: 90

        // 平均年龄
        double averageAge = people.stream()
                                  .collect(Collectors.averagingInt(Person::getAge));
        System.out.println(averageAge); // 输出: 30.0

        // 最大年龄
        Optional<Person> oldestPerson = people.stream()
                                              .collect(Collectors.maxBy(Comparator.comparingInt(Person::getAge)));
        oldestPerson.ifPresent(System.out::println); // 输出: Charlie (35)

        // 最小年龄
        Optional<Person> youngestPerson = people.stream()
                                                .collect(Collectors.minBy(Comparator.comparingInt(Person::getAge)));
        youngestPerson.ifPresent(System.out::println); // 输出: Bob (25)
    }
}

groupingBy

介绍

groupingBy 收集器用于根据某个属性对元素进行分组。它类似于SQL中的 GROUP BY 操作。

示例代码

class Product {
    private String category;
    private String name;

    public Product(String category, String name) {
        this.category = category;
        this.name = name;
    }

    public String getCategory() { return category; }
    public String getName() { return name; }

    @Override
    public String toString() {
        return name + " (" + category + ")";
    }
}

public class GroupingByExample {
    public static void main(String[] args) {
        List<Product> products = Arrays.asList(
            new Product("Electronics", "Laptop"),
            new Product("Electronics", "Smartphone"),
            new Product("Books", "Novel"),
            new Product("Books", "Textbook"),
            new Product("Electronics", "Tablet")
        );

        // 按类别分组
        Map<String, List<Product>> groupedProducts = products.stream()
                                                              .collect(Collectors.groupingBy(Product::getCategory));
        System.out.println(groupedProducts);
        // 输出:
        // {Electronics=[Laptop (Electronics), Smartphone (Electronics), Tablet (Electronics)],
        //  Books=[Novel (Books), Textbook (Books)]}
    }
}

进阶用法:多级分组

可以使用 Collectors.groupingBy 的嵌套来实现多级分组。

// 多级分组:先按类别,再按名称长度
Map<String, Map<Integer, List<Product>>> multiLevelGroup = products.stream()
    .collect(Collectors.groupingBy(
        Product::getCategory,
        Collectors.groupingBy(p -> p.getName().length())
    ));
System.out.println(multiLevelGroup);

partitioningBy

介绍

partitioningBy 收集器用于根据某个布尔条件将元素分为两组。它返回一个 Map<Boolean, List<T>>,其中键为 truefalse,分别对应满足和不满足条件的元素。

示例代码

public class PartitioningByExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 按是否为偶数分组
        Map<Boolean, List<Integer>> partitionedByEven = numbers.stream()
                                                                .collect(Collectors.partitioningBy(n -> n % 2 == 0));
        System.out.println(partitionedByEven);
        // 输出: {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8, 10]}
    }
}

joining

介绍

joining 收集器用于将流中的字符串元素连接成一个单一的字符串。可以指定分隔符、前缀和后缀。

示例代码

public class JoiningExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("Java", "Stream", "API");

        // 简单连接
        String joined = words.stream().collect(Collectors.joining());
        System.out.println(joined); // 输出: JavaStreamAPI

        // 使用分隔符
        String joinedWithComma = words.stream().collect(Collectors.joining(", "));
        System.out.println(joinedWithComma); // 输出: Java, Stream, API

        // 使用前缀和后缀
        String joinedWithPrefixSuffix = words.stream()
                                             .collect(Collectors.joining(", ", "[", "]"));
        System.out.println(joinedWithPrefixSuffix); // 输出: [Java, Stream, API]
    }
}
;