跳转到内容

Java Stream分组

来自代码酷


介绍[编辑 | 编辑源代码]

Java Stream分组是Java Stream API中的一个核心操作,允许开发者根据特定条件将流中的元素分类到不同的组中。通过Collectors.groupingBy()方法,可以轻松实现按属性、条件或自定义逻辑对数据进行分组,这在数据处理和分析中非常有用。分组操作返回一个Map,其中键是分组条件,值是对应的元素列表。

分组操作适用于以下场景:

  • 按属性分类(如按员工部门分组)
  • 按条件筛选(如按年龄范围分组)
  • 多级分组(如先按国家再按城市分组)

基础分组[编辑 | 编辑源代码]

最简单的分组形式是使用Collectors.groupingBy()按对象的某个属性分类。以下示例演示如何按字符串长度分组:

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

public class GroupingExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "fig");
        
        Map<Integer, List<String>> groupedByLength = words.stream()
            .collect(Collectors.groupingBy(String::length));
            
        System.out.println(groupedByLength);
    }
}

输出:

{3=[fig], 4=[date], 5=[apple], 6=[banana, cherry]}

解释:

  • String::length作为分类函数,生成分组的键(字符串长度)。
  • 结果是一个Map,键是长度,值是对应的单词列表。

多级分组[编辑 | 编辑源代码]

通过嵌套groupingBy()可以实现多级分组。以下示例先按字符串长度分组,再按首字母分组:

Map<Integer, Map<Character, List<String>>> multiLevelGrouping = words.stream()
    .collect(Collectors.groupingBy(
        String::length,
        Collectors.groupingBy(s -> s.charAt(0))
    ));

System.out.println(multiLevelGrouping);

输出:

{
    3={f=[fig]},
    4={d=[date]},
    5={a=[apple]},
    6={b=[banana], c=[cherry]}
}

分组后操作[编辑 | 编辑源代码]

可以对分组后的结果进一步处理,例如计算每组的数量或求和:

计数分组[编辑 | 编辑源代码]

Map<Integer, Long> countByLength = words.stream()
    .collect(Collectors.groupingBy(
        String::length,
        Collectors.counting()
    ));

System.out.println(countByLength);

输出:

{3=1, 4=1, 5=1, 6=2}

求和分组[编辑 | 编辑源代码]

假设有一个Product类,按类别分组并计算价格总和:

class Product {
    String category;
    double price;
    // 构造方法和getter省略
}

List<Product> products = Arrays.asList(
    new Product("Electronics", 999.99),
    new Product("Books", 19.99),
    new Product("Electronics", 499.99)
);

Map<String, Double> sumByCategory = products.stream()
    .collect(Collectors.groupingBy(
        Product::getCategory,
        Collectors.summingDouble(Product::getPrice)
    ));

System.out.println(sumByCategory);

输出:

{Electronics=1499.98, Books=19.99}

自定义分组逻辑[编辑 | 编辑源代码]

分组条件可以是任意Function。以下示例按奇偶性分组数字:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Map<String, List<Integer>> oddEvenGroups = numbers.stream()
    .collect(Collectors.groupingBy(
        n -> n % 2 == 0 ? "Even" : "Odd")
    );

System.out.println(oddEvenGroups);

输出:

{Odd=[1, 3, 5], Even=[2, 4]}

实际应用案例[编辑 | 编辑源代码]

案例1:订单按客户分组[编辑 | 编辑源代码]

class Order {
    String customerId;
    double amount;
    // 构造方法和getter省略
}

List<Order> orders = Arrays.asList(
    new Order("C101", 99.99),
    new Order("C102", 149.99),
    new Order("C101", 199.99)
);

Map<String, List<Order>> ordersByCustomer = orders.stream()
    .collect(Collectors.groupingBy(Order::getCustomerId));

案例2:日志按级别分组[编辑 | 编辑源代码]

List<String> logs = Arrays.asList(
    "ERROR: Disk full",
    "INFO: User logged in",
    "WARN: Connection timeout",
    "ERROR: File not found"
);

Map<String, List<String>> logsByLevel = logs.stream()
    .collect(Collectors.groupingBy(
        log -> log.split(":")[0]
    ));

性能考虑[编辑 | 编辑源代码]

  • 分组操作的时间复杂度通常为O(n),但多级分组会增加内存消耗。
  • 对于大型数据集,可结合parallelStream()并行处理(需注意线程安全)。

总结[编辑 | 编辑源代码]

Java Stream分组通过Collectors.groupingBy()提供强大的数据分类能力,支持:

  • 单级与多级分组
  • 分组后聚合操作(计数、求和等)
  • 完全自定义的分组逻辑

以下流程图说明分组过程:

graph LR A[原始流] --> B[应用分组条件] B --> C{生成Map} C --> D[键: 分组条件] C --> E[值: 元素列表]

掌握此技术可显著提升集合数据处理的效率和代码可读性。