天天看点

【Java】Java8 stream实现Collector接口来自定义收集器

文章目录

    • 一、前言
    • 二、代码
      • 2.1 从最简单的开始
            • 2.1.1 源码
            • 2.1.2 使用示例
            • 2.1.3 Person类
            • 2.1.4 运行结果
      • 2.2 接下来
            • 2.2.1 源码
            • 2.2.2 使用示例
            • 2.2.3 运行结果
      • 2.3 自定义收集器
            • 2.3.1 Collector接口源码解释
            • 2.3.2 自定义收集器实现
            • 2.3.3 使用示例
            • 2.3.4 运行结果

一、前言

  • 自从学习了Java 8函数是编程后,彻底的被Java 8所以吸引。其中最重要的一个优点就是Stream API 。stream和lambda表达式让我们平时对集合的操作更加的便捷,省去复杂的for循环嵌套,和繁琐的容器创建,让代码更加美观。
  • 在stream api 的终止操作中,Java已经为我们提供了许多方便好用的收集器,使我们能够从流中收集到我们想要的结果。但是有的时候内置的收集器也可能不能满足大家的需求,这个时候我们就可以利用Java预留的接口来定制自己的收集器了。

二、代码

2.1 从最简单的开始

  • 怎么用stream把一个Person类的集合按照年龄分组,得到结果Map<Integer,List< Person >>?
  • 其实这个在平时的开发中用到的挺多的,用Collectors.groupingBy()收集器很好实现实。
  • 这个方法需要传入一个Function,也就是获取key的Function

2.1.1 源码

public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier) {
    return groupingBy(classifier, toList());
}
           

2.1.2 使用示例

@Slf4j
public class NfzdTest {

	@Test
	public void test02() {
	    List<Person> data = this.getPersonList();
	    Map<Integer, List<Person>> collect = data.stream().collect(Collectors.groupingBy(Person::getAge));
	    collect.entrySet().forEach(a -> System.out.println("key:  " + a.getKey() + " value: " + a.getValue()));
	}
	
    private List<Person> getPersonList() {
        return Arrays.asList(
                new Person("小花", 12),
                new Person("小明", 14),
                new Person("小马", 14),
                new Person("小坏", 13),
                new Person("小红", 13),
                new Person("小赵", 14)
        );
    }
}
           

2.1.3 Person类

package com.lh.nfzd.model;

import com.lh.nfzd.common.annotation.CheckAge;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {

    private String name;

    @CheckAge(min = 5, max = 20)
    private Integer age;

}

           

2.1.4 运行结果

【Java】Java8 stream实现Collector接口来自定义收集器

2.2 接下来

  • 怎么从Person类的集合里按照年龄分组,获取姓名集合,得到结果Map<Integer,List< String >>。
  • 刚开始看到这个其实我也不知道怎们办,后来查了资料才有的结果。可以看到groupingBy方法的第二个参数是一个收集器,这个参数就是收集返回的map的value的收集器。可以用Collectors.groupingBy()、Collectors.mapping()组合使用来实现。

2.2.1 源码

public static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream) {
        return groupingBy(classifier, HashMap::new, downstream);
    }
           

2.2.2 使用示例

@Test
    public void test03() {
        List<Person> data = this.getPersonList();
        Map<Integer, List<String>> collect = data.stream().collect(Collectors.groupingBy(Person::getAge, Collectors.mapping(Person::getName, Collectors.toList())));
        collect.entrySet().forEach(a -> System.out.println("key:  " + a.getKey() + " value: " + a.getValue()));
    }
           

2.2.3 运行结果

【Java】Java8 stream实现Collector接口来自定义收集器

2.3 自定义收集器

  • 从上面的例子来看,其实我们想从stream中收集的结果各种各样,当有些想收集的结果jdk帮我们实现的时候,是很完美的。但当我们的需求复杂时,想收集的结果也更复杂,jdk没有帮我么实现,这个时候怎么办呢?
  • 其实jdk已经考虑到这个问题了,给我们提供了一个java.util.stream.Collector借口,我们只需要按照自己的需求实现符合自己的收集方法,就可以在stream中用自己的收集器了。下面我们用一个简答的例子实现下。

2.3.1 Collector接口源码解释

package java.util.stream;

import java.util.Collections;
import java.util.EnumSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
/*
 * T 第一个参数是待收集的元素类型 
 * A 第二个参数是累加器的类型
 * R 第三个参数是最终结果类型
 */
public interface Collector<T, A, R> {
    /**
     * 容器的提供者
     */
    Supplier<A> supplier();

    /**
     * 累加操作
     */
    BiConsumer<A, T> accumulator();

    /**
     * 并发的情况将每个线程的中间容器A合并
     */
    BinaryOperator<A> combiner();

    /**
     * 终止操作
     */
    Function<A, R> finisher();

    /**
     * 收集器特性
     */
    Set<Characteristics> characteristics();

    /**
     * 
     */
    public static<T, R> Collector<T, R, R> of(Supplier<R> supplier,
                                              BiConsumer<R, T> accumulator,
                                              BinaryOperator<R> combiner,
                                              Characteristics... characteristics) {
        Objects.requireNonNull(supplier);
        Objects.requireNonNull(accumulator);
        Objects.requireNonNull(combiner);
        Objects.requireNonNull(characteristics);
        Set<Characteristics> cs = (characteristics.length == 0)
                                  ? Collectors.CH_ID
                                  : Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH,
                                                                           characteristics));
        return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, cs);
    }

    /**
     * 
     */
    public static<T, A, R> Collector<T, A, R> of(Supplier<A> supplier,
                                                 BiConsumer<A, T> accumulator,
                                                 BinaryOperator<A> combiner,
                                                 Function<A, R> finisher,
                                                 Characteristics... characteristics) {
        Objects.requireNonNull(supplier);
        Objects.requireNonNull(accumulator);
        Objects.requireNonNull(combiner);
        Objects.requireNonNull(finisher);
        Objects.requireNonNull(characteristics);
        Set<Characteristics> cs = Collectors.CH_NOID;
        if (characteristics.length > 0) {
            cs = EnumSet.noneOf(Characteristics.class);
            Collections.addAll(cs, characteristics);
            cs = Collections.unmodifiableSet(cs);
        }
        return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, finisher, cs);
    }

    /**
     * 
     */
    enum Characteristics {
        /**
         * 如果一个收集器被标记为concurrent特性,那么accumulator 方法可以被多线程并发的的调用,
         * 并且只使用一个容器A.如果收集器被标记为concurrent,但是要操作的集合是有序的,那么
         * 最终得到的结果不能保证原来的顺序
         */
        CONCURRENT,

        /**
         * 适用于无序的集合
         */
        UNORDERED,

        /**
         * 如果收集器特性被设置IDENTITY_FINISH,那么会强制将中间容器A类型转换为结果类型R
         */
        IDENTITY_FINISH
    }
}

           

2.3.2 自定义收集器实现

package com.lh.nfzd.utils.collect;

import com.lh.nfzd.model.Person;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;

@Slf4j
public class NameCollector implements Collector<Person, Map<Integer, List<String>>, Map<Integer, List<String>>> {

    /**
     * 容器的提供者
     *
     * @return
     */
    @Override
    public Supplier<Map<Integer, List<String>>> supplier() {
        log.info("supplier invoke ....");
        return HashMap::new;
    }

    /**
     * 累加操作
     *
     * @return
     */
    @Override
    public BiConsumer<Map<Integer, List<String>>, Person> accumulator() {
        log.info("accumulator invoke ....");
        return (map, ele) -> {
            Integer age = ele.getAge();
            String name = ele.getName();
            if (map.containsKey(age)) {
                List<String> list = map.get(age);
                list.add(name);
                map.put(age, list);
            } else {
                List<String> list = new ArrayList<>();
                list.add(name);
                map.put(age, list);
            }
        };
    }

    /**
     * 并发的情况将每个线程的中间容器A合并
     *
     * @return
     */
    @Override
    public BinaryOperator<Map<Integer, List<String>>> combiner() {
        log.info("combiner invoke ....");
        return (left, right) -> {
            right.entrySet().forEach(a -> {
                Integer key = a.getKey();
                List<String> value = a.getValue();
                if (left.containsKey(key)) {
                    List<String> list = left.get(key);
                    list.addAll(value);
                    left.put(key, list);
                } else {
                    left.put(key, value);
                }
            });
            return left;
        };
    }

    /**
     * 终止操作
     * 这里可以不用终止操作 直接返回中间结果
     * characteristics 标记 IDENTITY_FINISH
     *
     * @return
     */
    @Override
    public Function<Map<Integer, List<String>>, Map<Integer, List<String>>> finisher() {
        log.info("finisher invoke ....");
        return (a) -> a;
    }

    @Override
    public Set<Characteristics> characteristics() {
        log.info("characteristics invoke ....");
        return Collections.unmodifiableSet(
                /*
                // finisher 不会执行
                EnumSet.of(
                        Characteristics.UNORDERED,
                        Characteristics.CONCURRENT,
                        Characteristics.IDENTITY_FINISH
                )
                */
                // finisher 会执行
                EnumSet.of(
                        Characteristics.UNORDERED,
                        Characteristics.CONCURRENT
                )
        );
    }
}

           

2.3.3 使用示例

@Test
    public void test09() {
        List<Person> data = this.getPersonList();
        Map<Integer, List<String>> collect = data.stream().collect(new NameCollector());
        collect.entrySet().forEach(a -> System.out.println("key:" + a.getKey() + " value:" + a.getValue()));
    }
           

2.3.4 运行结果

【Java】Java8 stream实现Collector接口来自定义收集器