4.5 分区与分组

问题

用户希望将元素集合分为若干个类别。

方案

Collectors.partitioningBy 方法将元素拆分为满足 Predicate 与不满足 Predicate 的两类。Collectors.groupingBy 方法生成一个由类别构成的 Map,其中值为每个类别中的元素。

讨论

假设存在一个由字符串构成的集合,可以通过 partitioningBy 方法将这些字符串按偶数长度和奇数长度进行划分,如例 4-20 所示。

例 4-20 根据偶数或奇数长度对字符串分区

  1. List<String> strings = Arrays.asList("this", "is", "a", "long", "list", "of",
  2. "strings", "to", "use", "as", "a", "demo");
  3. Map<Boolean, List<String>> lengthMap = strings.stream()
  4. .collect(Collectors.partitioningBy(s -> s.length() % 2 == 0));
  5. lengthMap.forEach((key,value) -> System.out.printf("%5s: %s%n", key, value));
  6. //
  7. // false: [a, strings, use, a]
  8. // true: [this, is, long, list, of, to, as, demo]

➊ 根据偶数或奇数长度进行分区

partitioningBy 方法包括两种形式:

  1. static <T> Collector<T,?,Map<Boolean,List<T>>> partitioningBy(
  2. Predicate<? super T> predicate)
  3. static <T,D,A> Collector<T,?,Map<Boolean,D>> partitioningBy(
  4. Predicate<? super T> predicate, Collector<? super T,A,D> downstream)

可以看到,返回类型中涉及泛型(generics),因此略显复杂,不过实际开发中很少会用到它们。两种 partitioningBy 方法的结果成为 collect 方法的参数,该方法使用生成的收集器来创建由第三个泛型参数定义的输出映射。

第一种 partitioningBy 方法传入单个 Predicate 作为参数,它将元素分为满足 Predicate 与不满足 Predicate 的两类。我们总是可以得到一个恰好包含两个条目的 Map,其中一个值列表满足 Predicate,另一个则不满足 Predicate

partitioningBy 方法的重载形式传入 Collector 作为第二个参数,它称为下游收集器。它支持对分区返回的列表进行后期处理,相关讨论请参见范例 4.6。

groupingBy 方法执行的操作类似于 SQL 的 GROUP BY 语句。该方法返回一个 Map,其中键为分组,值为各个分组中的元素列表。

4.5 分区与分组 - 图1 如果从数据库获取数据,请务必将分组操作放在数据库中执行,因为 Java 8 新增的方法适合处理内存中的数据。

groupingBy 方法的签名如下:

  1. static <T,K> Collector<T,?,Map<K,List<T>>> groupingBy(
  2. Function<? super T,? extends K> classifier)

Function 参数传入流的各个元素,并提取需要分组的元素。接下来,我们不是将字符串简单地分为两类,而是根据长度进行划分,如例 4-21 所示。

例 4-21 根据长度对字符串分组

  1. List<String> strings = Arrays.asList("this", "is", "a", "long", "list", "of",
  2. "strings", "to", "use", "as", "a", "demo");
  3. Map<Integer, List<String>> lengthMap = strings.stream()
  4. .collect(Collectors.groupingBy(String::length));
  5. lengthMap.forEach((k,v) -> System.out.printf("%d: %s%n", k, v));
  6. //
  7. // 1: [a, a]
  8. // 2: [is, of, to, as]
  9. // 3: [use]
  10. // 4: [this, long, list, demo]
  11. // 7: [strings]

➊ 根据长度进行分组

对于所生成的映射,键为字符串长度(1、2、3、4、7),值为各个长度的字符串列表。

另见

作为对本范例的延伸,范例 4.6 将讨论如何对 groupingBypartitioningBy 操作返回的列表进行后期处理。