Java 收集器工厂方法使用案例

以下是 Collectors 类中常用工厂方法的详细使用案例:

1. toList() - 收集到列表

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

public class CollectorExamples {
    public static void main(String[] args) {
        // 示例数据
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Alice");

        // toList() - 收集到列表
        List<String> nameList = names.stream()
            .filter(name -> name.length() > 3)
            .collect(Collectors.toList());
        System.out.println("toList 示例: " + nameList);
        // 输出: [Alice, Charlie, David, Alice] (保留重复元素)

        // 处理数值类型
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> squares = numbers.stream()
            .map(n -> n * n)
            .collect(Collectors.toList());
        System.out.println("数值平方列表: " + squares);
        // 输出: [1, 4, 9, 16, 25]
    }
}

2. toSet() - 收集到集合(去重)

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

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

        // toSet() - 自动去重
        Set<String> nameSet = names.stream()
            .filter(name -> name.length() > 3)
            .collect(Collectors.toSet());
        System.out.println("toSet 示例: " + nameSet);
        // 输出: [Alice, Charlie, David] (自动去重,顺序可能不同)

        // 自定义对象示例
        List<Employee> employees = Arrays.asList(
            new Employee("Alice", "HR", 5000),
            new Employee("Bob", "IT", 6000),
            new Employee("Alice", "IT", 5500),
            new Employee("Charlie", "HR", 5200)
        );

        // 根据姓名去重收集
        Set<String> uniqueNames = employees.stream()
            .map(Employee::getName)
            .collect(Collectors.toSet());
        System.out.println("员工姓名集合: " + uniqueNames);
        // 输出: [Alice, Bob, Charlie]
    }

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

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

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

3. toMap() - 收集到映射

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

public class CollectorExamples {
    public static void main(String[] args) {
        List<Employee> employees = Arrays.asList(
            new Employee("Alice", "HR", 5000),
            new Employee("Bob", "IT", 6000),
            new Employee("Charlie", "HR", 5200),
            new Employee("David", "IT", 6500)
        );

        // 简单的 toMap - 姓名作为键,员工对象作为值
        Map<String, Employee> employeeMap = employees.stream()
            .collect(Collectors.toMap(
                Employee::getName,  // 键提取器
                employee -> employee  // 值提取器
            ));
        System.out.println("员工映射: " + 
            employeeMap.keySet().stream()
                .map(key -> key + "=" + employeeMap.get(key).getDepartment())
                .collect(Collectors.joining(", ")));
        // 输出类似: Alice=HR, Bob=IT, Charlie=HR, David=IT

        // 处理键冲突的情况
        List<Employee> employeesWithDuplicates = Arrays.asList(
            new Employee("Alice", "HR", 5000),
            new Employee("Bob", "IT", 6000),
            new Employee("Alice", "Finance", 5500)  // 重复的键
        );

        Map<String, Employee> mergedMap = employeesWithDuplicates.stream()
            .collect(Collectors.toMap(
                Employee::getName,
                employee -> employee,
                (existing, replacement) -> {
                    // 处理键冲突:保留工资更高的员工
                    return existing.getSalary() >= replacement.getSalary() 
                        ? existing : replacement;
                }
            ));
        System.out.println("处理冲突后的映射: " + 
            mergedMap.entrySet().stream()
                .map(e -> e.getKey() + "=" + e.getValue().getDepartment())
                .collect(Collectors.joining(", ")));
        // 输出: Alice=Finance, Bob=IT

        // 指定特定的 Map 实现
        Map<String, Double> salaryMap = employees.stream()
            .collect(Collectors.toMap(
                Employee::getName,
                Employee::getSalary,
                (v1, v2) -> v1,  // 如果键重复,保留第一个值
                TreeMap::new  // 使用 TreeMap 保持排序
            ));
        System.out.println("排序的薪资映射: " + salaryMap);
        // 输出: {Alice=5000.0, Bob=6000.0, Charlie=5200.0, David=6500.0}
    }
}

4. groupingBy() - 分组收集

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

public class CollectorExamples {
    public static void main(String[] args) {
        List<Employee> employees = Arrays.asList(
            new Employee("Alice", "HR", 5000),
            new Employee("Bob", "IT", 6000),
            new Employee("Charlie", "HR", 5200),
            new Employee("David", "IT", 6500),
            new Employee("Eve", "Finance", 7000),
            new Employee("Frank", "Finance", 5500)
        );

        // 简单分组 - 按部门分组
        Map<String, List<Employee>> employeesByDept = employees.stream()
            .collect(Collectors.groupingBy(Employee::getDepartment));

        System.out.println("按部门分组:");
        employeesByDept.forEach((dept, empList) -> 
            System.out.println(dept + ": " + 
                empList.stream()
                    .map(Employee::getName)
                    .collect(Collectors.joining(", ")))
        );
        // 输出:
        // HR: Alice, Charlie
        // Finance: Eve, Frank
        // IT: Bob, David

        // 分组后对值进行进一步处理
        Map<String, Long> deptCount = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.counting()  // 统计每个部门的人数
            ));
        System.out.println("各部门人数: " + deptCount);
        // 输出: {HR=2, Finance=2, IT=2}

        // 分组后计算平均薪资
        Map<String, Double> avgSalaryByDept = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.averagingDouble(Employee::getSalary)
            ));
        System.out.println("各部门平均薪资: " + avgSalaryByDept);
        // 输出类似: {HR=5100.0, Finance=6250.0, IT=6250.0}

        // 多级分组 - 先按部门,再按薪资级别
        Map<String, Map<String, List<Employee>>> multiLevelGrouping = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.groupingBy(emp -> 
                    emp.getSalary() >= 6000 ? "高薪" : "普通"
                )
            ));

        System.out.println("\n多级分组结果:");
        multiLevelGrouping.forEach((dept, salaryGroup) -> {
            System.out.println("部门: " + dept);
            salaryGroup.forEach((level, empList) ->
                System.out.println("  " + level + ": " +
                    empList.stream()
                        .map(Employee::getName)
                        .collect(Collectors.joining(", ")))
            );
        });
        // 输出:
        // 部门: HR
        //   普通: Alice, Charlie
        // 部门: Finance
        //   高薪: Eve
        //   普通: Frank
        // 部门: IT
        //   高薪: Bob, David

        // 使用特定 Map 实现的分组
        Map<String, Set<String>> namesByDept = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                TreeMap::new,  // 使用 TreeMap 保持部门名称排序
                Collectors.mapping(
                    Employee::getName,
                    Collectors.toSet()  // 收集为 Set 去重
                )
            ));
        System.out.println("\n排序的部门-姓名映射: " + namesByDept);
        // 输出类似: {Finance=[Eve, Frank], HR=[Alice, Charlie], IT=[Bob, David]}
    }
}

5. joining() - 连接字符串

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

public class CollectorExamples {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Date");

        // 简单连接
        String simpleJoin = fruits.stream()
            .collect(Collectors.joining());
        System.out.println("简单连接: " + simpleJoin);
        // 输出: AppleBananaCherryDate

        // 使用分隔符连接
        String withDelimiter = fruits.stream()
            .collect(Collectors.joining(", "));
        System.out.println("带分隔符连接: " + withDelimiter);
        // 输出: Apple, Banana, Cherry, Date

        // 使用分隔符、前缀和后缀
        String withPrefixSuffix = fruits.stream()
            .collect(Collectors.joining(", ", "[", "]"));
        System.out.println("带前缀后缀连接: " + withPrefixSuffix);
        // 输出: [Apple, Banana, Cherry, Date]

        // 结合其他操作
        String filteredJoin = fruits.stream()
            .filter(fruit -> fruit.length() > 5)
            .map(String::toUpperCase)
            .collect(Collectors.joining(" | ", "Fruits: ", " (end)"));
        System.out.println("过滤转换后连接: " + filteredJoin);
        // 输出: Fruits: BANANA | CHERRY (end)

        // 处理对象列表
        List<Employee> employees = Arrays.asList(
            new Employee("Alice", "HR", 5000),
            new Employee("Bob", "IT", 6000),
            new Employee("Charlie", "HR", 5200)
        );

        String employeeNames = employees.stream()
            .map(Employee::getName)
            .collect(Collectors.joining(", ", "Employees: ", ""));
        System.out.println("员工姓名连接: " + employeeNames);
        // 输出: Employees: Alice, Bob, Charlie

        // 复杂连接示例
        String complexJoin = employees.stream()
            .map(emp -> emp.getName() + "(" + emp.getDepartment() + ")")
            .collect(Collectors.joining("; ", "[", "]"));
        System.out.println("复杂格式连接: " + complexJoin);
        // 输出: [Alice(HR); Bob(IT); Charlie(HR)]
    }
}

6. 综合示例

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

public class CollectorExamples {
    public static void main(String[] args) {
        List<Employee> employees = Arrays.asList(
            new Employee("Alice", "HR", 5000),
            new Employee("Bob", "IT", 6000),
            new Employee("Charlie", "HR", 5200),
            new Employee("David", "IT", 6500),
            new Employee("Eve", "Finance", 7000),
            new Employee("Frank", "Finance", 5500),
            new Employee("Grace", "IT", 6200)
        );

        // 综合应用:分析员工数据
        Map<String, String> departmentSummary = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.collectingAndThen(
                    Collectors.toList(),
                    list -> {
                        long count = list.size();
                        double avgSalary = list.stream()
                            .mapToDouble(Employee::getSalary)
                            .average()
                            .orElse(0.0);
                        String names = list.stream()
                            .map(Employee::getName)
                            .sorted()
                            .collect(Collectors.joining(", "));
                        return String.format("人数: %d, 平均薪资: %.2f, 员工: [%s]", 
                            count, avgSalary, names);
                    }
                )
            ));

        System.out.println("部门综合分析:");
        departmentSummary.forEach((dept, summary) -> 
            System.out.println(dept + " -> " + summary)
        );

        // 输出示例:
        // HR -> 人数: 2, 平均薪资: 5100.00, 员工: [Alice, Charlie]
        // Finance -> 人数: 2, 平均薪资: 6250.00, 员工: [Eve, Frank]
        // IT -> 人数: 3, 平均薪资: 6233.33, 员工: [Bob, David, Grace]
    }
}

7. 其他常用收集器

import java.util.*;
import java.util.stream.*;
import java.util.IntSummaryStatistics;

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

        // 统计信息
        IntSummaryStatistics stats = numbers.stream()
            .collect(Collectors.summarizingInt(Integer::intValue));
        System.out.println("统计信息: " + stats);
        System.out.println("平均值: " + stats.getAverage());
        System.out.println("总数: " + stats.getSum());
        System.out.println("最大值: " + stats.getMax());
        System.out.println("最小值: " + stats.getMin());
        System.out.println("数量: " + stats.getCount());

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

        // 归约操作
        Optional<Integer> sum = numbers.stream()
            .collect(Collectors.reducing((a, b) -> a + b));
        System.out.println("总和: " + sum.orElse(0));

        // 映射后再收集
        List<String> numberStrings = numbers.stream()
            .collect(Collectors.mapping(
                n -> "Number: " + n,
                Collectors.toList()
            ));
        System.out.println("映射后收集: " + numberStrings);
    }
}

总结

收集器 用途 特点
toList() 收集到列表 保留顺序,允许重复
toSet() 收集到集合 自动去重,不保证顺序
toMap() 收集到映射 需要键值提取器,可处理键冲突
groupingBy() 分组收集 强大的分组功能,支持多级分组
joining() 连接字符串 灵活配置分隔符、前缀、后缀

这些收集器可以组合使用,创建复杂的数据处理管道,是Java Stream API中非常强大的工具。