Java8收集器学习

·收集器是一种通用的,从流生成复杂值的结构。只需要把生成器作为参数传给collect()方法,任何流都可以使用它。
Java8中所有的收集器都是位于java.util.stream.Collectors这个包下面。

  1. 转换成其他集合

    Collectors.toList()收集器把流中数据转换成List对象。
    Collectors.toSet()收集器把流中数据转换成Set对象。
    Collectors.toCollection()收集器把流中数据转换成Collection对象。
    toCollection()收集器可以接受一个函数作为参数,用来指定生成的集合类型。比如指定生成的集合是TreeSet

    stream.collect(Collectors.toCollection(TreeSet::new));
    
  2. 转换成值

    maxByminBy收集器允许用户按照某种特定的顺序生成一个值,比如要找出成员最多的一个乐队。

    public Optional<Artist> biggestGroup(Stream<Artist> artists) {
        Function<Artist,Long> getCount = artist -> artist.getMembers().count();
        return artists.collect(Collectors.maxBy(Comparators.comparing(getCount)));
    }
    

    averagingInt收集器接受一个Lambda表达式作为参数,将流中的元素转换成为一个整数,然后计算平均值(同时存在doublelong类型的重载方法)。比如要计算一组专辑中曲目的平均数。

    public double averageNumberOfTracks(List<Album> albums) {
        return albums.stream()
                     .collect(Collectors.averagingInt(album -> album.getTrackList().size()));
    }
    
  3. 数据分块
    数据分块是指将数据流分成两个集合。
    partitioningBy收集器接受一个Predicate对象作为参数,把一个流分成两部分,并根据布尔值返回一个Map到列表中。

    public class Main {
        public static void main(String[] args) {
            List<Integer> list = Stream.of(1,2,3,4,5,9,10,22)
                                .collect(Collectors.toList());
            Map<Boolean,List<Integer>> resultMap = list.stream()
                                .collect(Collectors.partitioningBy(input -> input > 5));
            System.out.println(resultMap);
        }
    }
    

    输出结果为

     {false=[1, 2, 3, 4, 5], true=[9, 10, 22]}
    
  4. 数据分组

    与数据分块只能将数据分为truefalse不同,数据分组可以使用任意值对数据进行分组。
    groupingBy收集器实现数据分组功能。
    公司雇员定义如下:

    public class Employee {
        private String company;
        private String name;
        private int level;
        public Employee(String company,String name,int level) {
            this.company = company;
            this.name = name;
            this.level = level;
        }        
    }
    

    现在需要把员工按公司分组

    public class Main {
        public static void main(String[] args) {
            Employee e1 = new Employee("alibaba","Slogen",5);
            Employee e2 = new Employee("alibaba","JiaChen",6);
            Employee e3 = new Employee("didi","TangMen",6);
            Employee e4 = new Employee("xinmeida","Slogan",4);
            List<Employee> employees = Stream.of(e1,e2,e3,e4).collect(Collectors.toList());
            Map<String,List<Employee>> map = employees.stream().collect(Collectors.groupingBy(Employee::getCompany));
           map.keySet().stream().forEach(company -> System.out.println(company + ":" + map.get(company)));
        }
    }
    

    map输出结果为:

    alibaba:[Employee(company=alibaba, name=Slogen, level=5), Employee(company=alibaba, name=JiaChen, level=6)]
    didi:[Employee(company=didi, name=TangMen, level=6)]
    xinmeida:[Employee(company=xinmeida, name=Slogan, level=4)]
    
  5. 字符串
    Collectors.joining收集器能够很方便的从一个流中得到一个字符串,允许用户提供一个分隔符(用以分割元素)、前缀和后缀。

     public class Main {
        public static void main(String[] args) {
            List<String> names = Stream.of("slogen","jiachen","tangmen","slogan").collect(Collectors.toList());
            System.out.println(names.stream().collect(Collectors.joining("|","<",">")));
        }
    }
    

    输出为

     <slogen|jiachen|tangmen|slogan>
    
  6. 组合收集器
    多个收集器可以组合在一起使用,实现强大的效果。
    groupingBy收集器接一个收集器作为参数,这个当做参数的收集器叫做下游收集器,用以收集最后结果的一个子集。
    还是以上面的雇员为例,我们不需要把员工按照公司分组,我们需要知道的是每个公司的员工人数,则grouping()的第二个参数可以设置为收集器counting(),这样主收集器grouping()每一次的收集结果会传递给下游收集器counting()做二次收集,也就是统计个数。

    public class Main {
        public static void main(String[] args) {
            Employee e1 = new Employee("alibaba","Slogen",5);
            Employee e2 = new Employee("alibaba","JiaChen",6);
            Employee e3 = new Employee("didi","TangMen",6);
            Employee e4 = new Employee("xinmeida","Slogan",4);
            List<Employee> employees = Stream.of(e1,e2,e3,e4).collect(Collectors.toList());
            // 第二个参数设置为counting()收集器,用于收集主收集器每次收集到的数据的个数
            Map<String,Long> map = employees.stream().collect(
                    Collectors.groupingBy(Employee::getCompany,counting()));
            System.out.println(map);
        }
    }
    

    输出结果为

    {alibaba=2, didi=1, xinmeida=1}
    

    同样的,假设想要获取公司所有雇员的名字,按照常规的思路是先按照公司对雇员进行分类,然后再对每个公司的雇员进行遍历,获取雇员的名字,代码如下:

    public class Main {
        public static void main(String[] args) {    
            Employee e1 = new Employee("alibaba","Slogen",5);
            Employee e2 = new Employee("alibaba","JiaChen",6);
            Employee e3 = new Employee("didi","TangMen",6);
            Employee e4 = new Employee("xinmeida","Slogan",4);
            List<Employee> employees = Stream.of(e1,e2,e3,e4).collect(Collectors.toList());
            // 1. 先把雇员按照公司进行分类
            Map<String,List<Employee>> map = employees.stream().collect(Collectors.groupingBy(Employee::getCompany));
            Map<String,List<String>> result = new HashMap<>();
            // 2. 遍历上一步结果,获取每个雇员的名字
            map.forEach((key,value) -> result.put(key,value.stream().map(Employee::getName).collect(Collectors.toList())));
            System.out.println(result);
        }
    }
    

    上面的方法虽然也能够实现功能,但是不够lambda不够优雅。对于这样的场景,Oracle提供了mapping收集器,这个收集器作为下游收集器,可以执行类似于map的操作.

    public class Main {
        public static void main(String[] args) {
            Employee e1 = new Employee("alibaba","Slogen",5);
            Employee e2 = new Employee("alibaba","JiaChen",6);
            Employee e3 = new Employee("didi","TangMen",6);
            Employee e4 = new Employee("xinmeida","Slogan",4);
            List<Employee> employees = Stream.of(e1,e2,e3,e4).collect(Collectors.toList());
            // mapping收集器作为下游收集器,对主收集器每次收集到的数据做二次收集,并把收集结果放到一个list中
            Map<String,List<String>> map = employees.stream().collect(Collectors.groupingBy(Employee::getCompany,Collectors.mapping(Employee::getName,Collectors.toList())));
            System.out.println(map);
        }
    }
    
  7. 自定义收集器
    todo

2018-04-21 15:0327