天天看點

【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接口來自定義收集器