Java8中重要的收集器Collector

來源:互聯網
上載者:User

標籤:lock   dal   可變   結構   類型   params   內部類   java8   keyword   

Collector介紹

Java8的stream api能很方便我們對資料進行統計分類等工作,函數式編程的風格讓我們方便並且直觀地編寫統計代碼。

例如:

Stream<Integer> stream = Stream.iterate(1, item -> item+2).limit(6);//        stream.filter(item -> item > 2).mapToInt(item -> item * 2).skip(2).limit(2).sum();//.reduce(0, (val, val2)->val+val2);//        System.out.println(sum);IntSummaryStatistics summaryStatistics = stream.filter(item -> item > 2).mapToInt(item -> item * 2).skip(2).summaryStatistics();System.out.println(summaryStatistics.getAverage());

stream裡有一個collect(Collector c)方法,這個方法裡面接收一個Collector的執行個體。這裡我們要弄清楚Collector與Collectors之間的關係。

作為collect方法的參數,Collector是一個介面,它是一個可變的匯聚操作,將輸入元素累計到一個可變的結果容器中;它會在所有元素都處理完畢後,將累積的結果轉換為一個最終的表示(這是一個可選操作);

這些如果你不太懂,請繼續往下看,結合下面自訂Collector,相信你可以理解這些內容。

Collectors本身提供了關於Collector的常見匯聚實現,Collectors的內部類CollectorImpl實現了Collector介面,Collectors本身實際上是一個工廠。

Collector的使用

很多時候我們會用到Collectors的toList方法,Collectors中提供了將流中元素累積到匯聚結果的各種方式,例如Counting、Joining、maxBy等等。下面是一個例子:

//分組        List<Student> list1 = Arrays.asList(s1, s2, s3, s4, s5);        //根據名稱分組//        Map<String, List<Student>> map = list1.stream().collect(Collectors.groupingBy(Student::getName));        //先根據名稱分組,然後求每組平均分        Map<String, Double> map  = list1.stream().collect(Collectors.groupingBy(Student::getName, Collectors.averagingDouble(Student::getScore)));        System.out.println(map);        //分區        Map<Boolean, List<Student>> map1 = list1.stream().collect(Collectors.partitioningBy(item -> item.getScore() >= 90));        System.out.println(map1);        System.out.println("------2-----");        //先根據名稱分組再根據分數分組        Map<String, Map<Integer, List<Student>>> map2 = list1.stream().collect(Collectors.groupingBy(Student::getName, Collectors.groupingBy(Student::getScore)));        System.out.println(map2);

這裡分區分組與資料庫中的分區分組的概念類似。

Collectors are designed to be composed; many of the methods in {@link Collectors} are functions that take a collector and produce a new collector.

附上javadoc上的一句話,這句話說明收集操作是可以嵌套的。

自訂Collector

前面講過,Collectors本身提供了關於Collector的常見匯聚實現,那麼程式員自身也可以根據情況定義自己的匯聚實現。

首先我們看下Collector介面的結構

public interface Collector<T, A, R> {     Supplier<A> supplier();    BiConsumer<A, T> accumulator();    BinaryOperator<A> combiner();    Function<A, R> finisher();    Set<Characteristics> characteristics();}

其中這裡的泛型所表示的含義是:
T:表示流中每個元素的類型。
A:表示中間結果容器的類型。
R:表示最終返回的結果類型。

Collector中還定義了一個枚舉類Characteristics,有三個枚舉值,理解這三個值的含義對於我們自己編寫正確的收集器也是至關重要的。

  • Characteristics.CONCURRENT:表示中間結果只有一個,即使在並行流的情況下。所以只有在並行流且收集器不具備CONCURRENT特性時,combiner方法返回的lambda運算式才會執行(中間結果容器只有一個就無需合并)。
  • Characteristics.UNORDER:表示流中的元素無序。
  • Characteristics.IDENTITY_FINISH:表示中間結果容器類型與最終結果類型一致,此時finiser方法不會被調用。

我們再來看一下Collectors中toList方法的實現。

public static <T> Collector<T, ?, List<T>> toList() {        return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,                                   (left, right) -> { left.addAll(right); return left; },                                   CH_ID);    }static final Set<Collector.Characteristics> CH_ID            = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));

 

到此,不知你是否對自訂收集器有那麼點感覺了?

那麼到底怎麼實現自訂收集器呢,下面舉例子來看看。

public class MyCollectorImpl<T> implements Collector<T, Set<T>, Set<T>> {    @Override    public Supplier<Set<T>> supplier() {        return HashSet<T>::new;    }    @Override    public BiConsumer<Set<T>, T> accumulator() {        return Set<T>::add;    }    @Override    public BinaryOperator<Set<T>> combiner() {        return (set, item) -> {set.addAll(item); return set;};    }    @Override    public Function<Set<T>, Set<T>> finisher() {        return Function.identity();    }    @Override    public Set<Characteristics> characteristics() {        return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH,UNORDERED));    }    public static void main(String[] args) {        List<String> list = Arrays.asList("hello", "world", "welcome");        Set<String> set = list.stream().collect(new MyCollectorImpl<>());        set.forEach(System.out::println);    }}

這是一個簡單的例子,我們給收集器加上了IDENTITY_FINISH特性,此時finisher方法返回的lambda運算式是不會得到調用的。這一點也可以從源碼中得到驗證。

例2:

public class MyCollectorImpl2<T> implements Collector<T, Set<T>, Map<T, T>>{    @Override    public Supplier<Set<T>> supplier() {        return HashSet<T>::new;    }    @Override    public BiConsumer<Set<T>, T> accumulator() {        return Set<T>::add;    }    @Override    public BinaryOperator<Set<T>> combiner() {        return (set1, set2)->{            set1.addAll(set2);            return set1;        };    }    @Override    public Function<Set<T>, Map<T, T>> finisher() {        return (set)->{            HashMap<T, T> map = new HashMap();            set.stream().forEach((item)->map.put(item, item));            return map;        };    }    @Override    public Set<Characteristics> characteristics() {        return Collections.unmodifiableSet(EnumSet.of(Characteristics.UNORDERED));    }    public static void main(String[] args) {        List<String> list = Arrays.asList("hello", "world", "welcome");        HashSet<String> set = new HashSet<>();        set.addAll(list);        Map<String, String> map = set.stream().collect(new MyCollectorImpl2<>());        System.out.println(map);    }}

這個例子中由於中間累計結果容器的類型與最終類型不一致,在finisher方法中必須做正確的處理,否則肯定拋出類型轉換的異常。

Java8中重要的收集器Collector

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.