Collectors 详解

Java 8 引入的 Collectors 类是一个工具类,提供了一系列预定义的收集器实现,用于简化流(Stream)的收集操作。它具有以下优势:简化和统一了收集操作,提高了代码的可读性。Collectors 常用的静态方法包括将元素收集到集合(如 List、Set)、转换为其他类型(如 Map)、进行聚合元素操作(如计数、求和、平均值)、查找最大值和最小值、分组、分区以及连接字符串等。通过这些方法,可以轻松实现复杂的流处理逻辑,大大提高了开发效率。


目录

一、什么是 Collectors

二、Collectors 的优势

三、Collectors 常用的静态方法

1. 将元素收集到集合中

2. 转换为其他类型

3. 聚合元素

4. 最大值、最小值

5. 分组

6. 分区

7. 连接字符串


前置知识:Stream API

可参考 Stream API 详解和25道练习题-CSDN博客

一、什么是 Collectors

Collectors 是 Java 8 及更高版本中引入的一个工具类,它提供了许多静态方法,这些方法返回实现了 Collector 接口的实例。Collector 接口定义了如何将流(Stream)中的元素累积成一个结果容器,这个结果容器可以是任何最终结果类型,例如一个集合、一个值或者一个映射。

二、Collectors 的优势

  • 简化和统一了收集操作:在 Java 8 之前,将流(Stream)中的元素收集到集合或执行其他终端操作需要编写大量的样板代码。Collectors 提供了预定义的收集器,使得这些操作变得简单和统一。
  • 提高了代码的可读性:使用 Collectors 可以用一行代码替换原本需要多行代码的收集逻辑,这使得代码更加简洁和易于理解。

三、Collectors 常用的静态方法

// 将元素收集到集合中

  • Collectors.toList()
  • Collectors.toSet()
  • Collectors.toCollection(Supplier<Collection<? super T>> collectionFactory)

// 转换为其他类型

  • Collectors.toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
  • Collectors.toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)
  • Collectors.toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)
  • Collectors.toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
  • Collectors.toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)
  • Collectors.toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)
  • Collectors.collectingAndThen(Collector<T, A, R> downstream, Function<R,RR> finisher)

// 聚合元素

  • Collectors.counting()
  • Collectors.summingInt(ToIntFunction<? super T> mapper)
  • Collectors.summingLong(ToLongFunction<? super T> mapper)
  • Collectors.summingDouble(ToDoubleFunction<? super T> mapper)
  • Collectors.averagingInt(ToIntFunction<? super T> mapper)
  • Collectors.averagingLong(ToLongFunction<? super T> mapper)
  • Collectors.averagingDouble(ToDoubleFunction<? super T> mapper)
  • Collectors.reducing(T identity, BinaryOperator<T> accumulator)
  • Collectors.reducing(BinaryOperator<T> accumulator)
  • Collectors.reducing(U identity, Function<? super T, ? extends U> mapper, BinaryOperator<U> accumulator)

// 最大值、最小值

  • Collectors.maxBy(Comparator<? super T> comparator)
  • Collectors.minBy(Comparator<? super T> comparator)

// 分组

  • Collectors.groupingBy(Function<? super T, ? extends K> classifier)
  • Collectors.groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)
  • Collectors.groupingBy(Function<? super T, ? extends K> classifier, Supplier<M> mapFactory, Collector<? super T, A, D> downstream)

// 分区

  • Collectors.partitioningBy(Predicate<? super T> predicate)
  • Collectors.partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downstream)

// 连接字符串

  • Collectors.joining()
  • Collectors.joining(CharSequence delimiter)
  • Collectors.joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
     

1. 将元素收集到集合中

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

public class Main {
    public static void main(String[] args) {
        List<String> strings = Arrays.asList("a", "b", "c", "a", "b", "c");

        // 收集到List
        List<String> list = strings.stream().collect(Collectors.toList());
        //List<String> list = new ArrayList<>(strings);
        
        System.out.println(list);//[a, b, c, a, b, c]

        // 收集到Set
        Set<String> set = strings.stream().collect(Collectors.toSet());
        //Set<String> set = new HashSet<>(strings);
        System.out.println(set);//[a, b, c]

        // 收集到指定的集合类型,如ArrayList, LinkList, HashSet, TreeSet, PriorityQueue等
        ArrayList<String> arrayList = strings.stream().collect(Collectors.toCollection(ArrayList::new));
        //ArrayList<String> arrayList = new ArrayList<>(strings);
        System.out.println(arrayList);//[a, b, c, a, b, c]
    }
}

2. 转换为其他类型

(1)Collectors.toMap

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

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

        Map<String, Integer> map = people.stream()
                .collect(Collectors.toMap(
                        person -> person.getName(),  // 键映射器
                        person -> person.getAge()    // 值映射器
                ));

        System.out.println(map); // 输出:{Bob=30, Alice=25, Charlie=35}

        // people.add(new Person("Charlie", 33));   // 再插入重复的键会抛出异常
    }
}

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;
    }
}

(2)Collectors.toMap 有合并函数

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

public class Main {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Alice", 25),
                new Person("Alice", 30),
                new Person("Alice", 35)
        ); // 注意有相同的名字

        Map<String, Integer> map = people.stream()
                .collect(Collectors.toMap(
                        Person::getName,            // 键映射器
                        Person::getAge,             // 值映射器
                        (age1, age2) -> age1 + age2 // 合并函数,遇到重复的键则合并
                ));

        System.out.println(map); // 输出:{Alice=55}
    }
}


class Person {
    // 同上...
}

(3)Collectors.toMap 有合并函数和映射供应器

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

public class Main {
    public static void main(String[] args) {
        List<Person> people = List.of(
                new Person("David", 20),
                new Person("Alice", 25),
                new Person("Alice", 35),
                new Person("Bob", 30)
        );

        Map<String, Integer> map = people.stream()
                .collect(Collectors.toMap(
                        Person::getName, // 键映射器
                        Person::getAge,  // 值映射器
                        (existingValue, newValue) -> existingValue, // 合并函数,这里选择保留旧值
                        TreeMap::new     // 映射供应器,这里使用 TreeMap 来保持键的顺序
                ));

        System.out.println(map); // 输出:{Alice=25, Bob=30, David=20} 字母序递增
    }
}

class Person {
    // 同上...
}

(4)Collectors.toConcurrentMap

Collectors.toConcurrentMap

  • 线程安全:生成的 ConcurrentMap 设计用于在多线程环境中使用,它提供了足够的线程安全性和并发性能。
  • 默认映射供应器:如果没有提供特定的映射供应器,则默认使用 ConcurrentHashMap。

Collectors.toMap

  • 非线程安全:生成的Map在多线程环境中可能会遇到并发修改问题。
  • 默认映射供应器:如果没有提供特定的映射供应器,则默认使用 HashMap。

以下为Collectors.toConcurrentMap的简单示例,Collectors.toConcurrentMap 有合并函数和映射供应器参考上面的(2)和(3)。

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

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

        ConcurrentMap<String, Integer> concurrentMap = people.stream()
                .collect(Collectors.toConcurrentMap(Person::getName, Person::getAge));

        System.out.println(concurrentMap); // {Bob=30, Alice=25}
    }
}

class Person {
    // 同上...
}

(5)Collectors.collectingAndThen

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

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

        // 使用 collectingAndThen 来收集名字到一个列表,然后对其进行排序
        List<String> sortedNames = people.stream()
                .map(Person::getName) // 将 Person 对象映射到它们的名字
                .collect(Collectors.collectingAndThen(
                        Collectors.toList(), // 下游收集器,先将名字收集到列表中
                        list -> {            // 然后执行排序操作
                            list.sort(String::compareTo); // 完成函数,对列表进行排序
                            return list;
                        }
                ));

        System.out.println(sortedNames); // 输出:[Alice, Bob, Charlie]
    }
}
class Person {
    // 同上...
}

3. 聚合元素

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

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

        // Collectors.counting() 统计元素个数
        long count = people.stream().collect(Collectors.counting());
        // 或 people.size();
        System.out.println("人数: " + count); // 输出:人数: 3

        // Collectors.summingInt(ToIntFunction<? super T> mapper) 对整数属性求和
        int totalAge = people.stream().collect(Collectors.summingInt(Person::getAge));
        // 或 people.stream().mapToInt(Person::getAge).sum();
        System.out.println("总年龄: " + totalAge); // 输出:总年龄: 90

        // Collectors.summingLong(ToLongFunction<? super T> mapper) 对长整数属性求和
        long totalAgeLong = people.stream().collect(Collectors.summingLong(p -> (long) p.getAge()));
        // 或 people.stream().mapToLong(p -> (long) p.getAge()).sum();
        System.out.println("总年龄(长整数): " + totalAgeLong); // 输出:总年龄(长整数): 90

        // Collectors.summingDouble(ToDoubleFunction<? super T> mapper) 对双精度浮点数属性求和
        double totalAgeDouble = people.stream().collect(Collectors.summingDouble(p -> (double) p.getAge()));
        // 或 people.stream().mapToDouble(p -> (double) p.getAge()).sum();
        System.out.println("总年龄(双精度): " + totalAgeDouble); // 输出:总年龄(双精度): 90.0

        // Collectors.averagingInt(ToIntFunction<? super T> mapper) 计算整数属性的平均值
        double averageAge = people.stream().collect(Collectors.averagingInt(Person::getAge));
        System.out.println("平均年龄: " + averageAge); // 输出:平均年龄: 30.0

        // Collectors.averagingLong(ToLongFunction<? super T> mapper) 计算长整数属性的平均值
        double averageAgeLong = people.stream().collect(Collectors.averagingLong(p -> (long) p.getAge()));
        System.out.println("平均年龄(长整数): " + averageAgeLong); // 输出:平均年龄(长整数): 30.0

        // Collectors.averagingDouble(ToDoubleFunction<? super T> mapper) 计算双精度浮点数属性的平均值
        double averageAgeDouble = people.stream().collect(Collectors.averagingDouble(p -> (double) p.getAge()));
        System.out.println("平均年龄(双精度): " + averageAgeDouble); // 输出:平均年龄(双精度): 30.0

        // Collectors.reducing(T identity, BinaryOperator<T> accumulator) 使用提供的初始值和累加器进行归约
        String names = people.stream().map(Person::getName).collect(Collectors.reducing("", (s1, s2) -> s1 + ", " + s2));
        // 或 people.stream().map(Person::getName).reduce("", (s1, s2) -> s1 + ", " + s2);
        System.out.println("名字列表: " + names); // 输出:名字列表: , Alice, Bob, Charlie

        // Collectors.reducing(BinaryOperator<T> accumulator) 不提供初始值,返回 Optional
        Optional<String> firstAndLast = people.stream().map(Person::getName).collect(Collectors.reducing((s1, s2) -> s1 + ", " + s2));
        // 或 people.stream().map(Person::getName).reduce((s1, s2) -> s1 + ", " + s2);
        firstAndLast.ifPresent(s -> System.out.println("名字列表(无初始值): " + s)); // 输出:名字列表(无初始值): Alice, Bob, Charlie

        // Collectors.reducing(U identity, Function<? super T, ? extends U> mapper, BinaryOperator<U> accumulator) 使用映射器和累加器进行归约
        Person reducedPerson = people.stream()
                .collect(Collectors.reducing(
                        new Person("", 0), // 初始值
                        person -> new Person(person.getName(), person.getAge()+1), // 映射器
                        (p1, p2) -> new Person(p1.getName() + ", " + p2.getName(), p1.getAge() + p2.getAge()) // 累加器
                ));

        System.out.println("归约后的 Person 对象: " + reducedPerson.getName() + " --- " + reducedPerson.getAge());
        // 输出:归约后的 Person 对象: Alice, Bob, Charlie --- 93


    }
}


class Person {
    // 同上...
}

4. 最大值、最小值

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

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

        // 使用 Collectors.maxBy 找到年龄最大的 Person
        Optional<Person> oldest = people.stream()
                .collect(Collectors.maxBy(Comparator.comparingInt(Person::getAge)));
        // 或people.stream().max(Comparator.comparingInt(Person::getAge));
        oldest.ifPresent(person -> System.out.println("年龄最大的 Person: " + person.getName() + " - " + person.getAge()));
        // 输出:年龄最大的 Person: Charlie - 35

        // 使用 Collectors.minBy 找到年龄最小的 Person
        Optional<Person> youngest = people.stream()
                .collect(Collectors.minBy(Comparator.comparingInt(Person::getAge)));
        // 或people.stream().min(Comparator.comparingInt(Person::getAge));
        youngest.ifPresent(person -> System.out.println("年龄最小的 Person: " + person.getName() + " - " + person.getAge()));
        // 输出:年龄最小的 Person: Alice - 25
    }
}


class Person {
    // 同上...
}

5. 分组

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

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

        // 使用 Collectors.groupingBy 分组并计数
        Map<Integer, Long> peopleByAge = people.stream()
                .collect(Collectors.groupingBy(Person::getAge, Collectors.counting()));

        System.out.println("按年龄分组的人数: " + peopleByAge);
        // 输出:按年龄分组的人数: {35=1, 25=2, 30=2}

        // 使用 Collectors.groupingBy 分组并计算平均年龄
        Map<Integer, Double> averageAgeByGroup = people.stream()
                .collect(Collectors.groupingBy(Person::getAge, Collectors.averagingInt(Person::getAge)));

        System.out.println("每个年龄组的平均年龄: " + averageAgeByGroup);
        // 输出:每个年龄组的平均年龄: {35=35.0, 25=25.0, 30=30.0}

        // 使用 Collectors.groupingBy 分组,并提供自定义的 mapFactory
        Map<Integer, List<Person>> peopleByAgeWithCustomMap = people.stream()
                .collect(Collectors.groupingBy(Person::getAge, () -> new LinkedHashMap<>(), Collectors.toList()));

        System.out.println("按年龄分组的人员列表(使用自定义 mapFactory): " + peopleByAgeWithCustomMap);
        // 输出:按年龄分组的人员列表(使用自定义 mapFactory): {25=[Alice, Charlie], 30=[Bob, David], 35=[Eve]}
    }
}
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;
    }
}

6. 分区

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

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

        // 使用 Collectors.partitioningBy 根据 age > 30 分组
        Map<Boolean, List<Person>> partitionedByAge = people.stream()
                .collect(Collectors.partitioningBy(person -> person.getAge() > 30));

        System.out.println("根据年龄是否大于30分组: " + partitionedByAge);
        // 输出:根据年龄是否大于30分组: {false=[Alice, Eve], true=[Bob, Charlie, David]}

        // 使用 Collectors.partitioningBy 根据 age > 30 分组,并计算每组的平均年龄
        Map<Boolean, Double> averageAgeByPartition = people.stream()
                .collect(Collectors.partitioningBy(person -> person.getAge() > 30,
                        Collectors.averagingInt(Person::getAge)));

        System.out.println("每组的平均年龄: " + averageAgeByPartition);
        // 输出:每组的平均年龄: {false=26.5, true=36.0}
    }
}

class Person{
    // 同上...
}

7. 连接字符串

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

public class Main {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Alice",19),
                new Person("Bob",20),
                new Person("Charlie",22)
        );

        // 使用 Collectors.joining() 连接名字,不添加分隔符
        String names = people.stream()
                .map(Person::getName)
                .collect(Collectors.joining());

        System.out.println("连接名字(无分隔符): " + names);
        // 输出:连接名字(无分隔符): AliceBobCharlie

        // 使用 Collectors.joining(CharSequence delimiter) 连接名字,添加分隔符
        String namesWithComma = people.stream()
                .map(Person::getName)
                .collect(Collectors.joining(", "));

        System.out.println("连接名字(添加逗号分隔符): " + namesWithComma);
        // 输出:连接名字(添加逗号分隔符): Alice, Bob, Charlie

        // 使用 Collectors.joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) 连接名字,添加分隔符、前缀和后缀
        String namesWithPrefixSuffix = people.stream()
                .map(Person::getName)
                .collect(Collectors.joining(", ", "Names = [", "]"));
        //.collect(Collectors.collectingAndThen(Collectors.joining(", "), s -> "[" + s + "]"));

        System.out.println("连接名字(添加分隔符、前缀和后缀): " + namesWithPrefixSuffix);
        // 输出:连接名字(添加分隔符、前缀和后缀): Names = [Alice, Bob, Charlie]
    }
}

class Person {
    // 同上...
}

  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`Collectors.collectingAndThen()` 是一个非常有用的方法,它可以将一个收集器(Collector)转换为另一个收集器或者一个最终的结果。 它的方法签名如下: ```java public static <T, A, R, RR> Collector<T, A, RR> collectingAndThen(Collector<T, A, R> downstream, Function<R, RR> finisher) ``` 其中,`downstream` 参数是需要转换的收集器,`finisher` 参数是一个函数,它将最终的结果类型 `R` 转换为另一个类型 `RR`。 `Collectors.collectingAndThen()` 方法的作用是,首先使用 `downstream` 收集器收集元素,然后将收集的结果交给 `finisher` 函数进行最终的转换,得到最终的结果 `RR`。 例如,可以将 `Collectors.toList()` 收集器转换为一个 `List` 类型的最终结果: ```java List<String> strings = Arrays.asList("a", "b", "c", "d", "e"); List<String> result = strings.stream() .collect(Collectors.collectingAndThen(Collectors.toList(), ArrayList::new)); ``` 在上面的示例中,我们将一个字符串列表转换为一个新的 `ArrayList` 对象,使用了 `Collectors.collectingAndThen()` 方法,将 `Collectors.toList()` 收集器转换为一个 `List` 类型的最终结果。 除了将收集器转换为最终结果类型之外,`Collectors.collectingAndThen()` 方法还可以将收集器转换为另一个收集器。例如,可以将一个收集器转换为一个 `Map` 类型的收集器: ```java List<String> strings = Arrays.asList("a", "bb", "ccc", "dddd", "eeeee"); Map<Integer, String> result = strings.stream() .collect(Collectors.collectingAndThen( Collectors.toMap(String::length, Function.identity()), Collections::unmodifiableMap)); ``` 在上面的示例中,我们将一个字符串列表按照长度进行分组,然后使用 `Collectors.collectingAndThen()` 方法,将 `Collectors.toMap()` 收集器转换为一个不可修改的 `Map` 类型的最终结果。这里的 `Function.identity()` 方法用于将字符串本身作为 `Map` 的值,`Collections::unmodifiableMap` 方法用于将 `Map` 包装为不可修改的形式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值