Java8特性④Stream收集數據

收集器能夠簡潔而靈活地定義collect用來生成結果集合的標準。更具體地說,對流調用 collect 方法將對流中的元素觸發一個歸約操做(由Collector來參數化)。通常來講,Collector 會對元素應用一個轉換函數(不少時候是不體現任何效果的恆等轉換, 例如 toList ),並將結果累積在一個數據結構中,從而產生這一過程的最終輸出。下面就來學習那些能夠從Collectors 類提供的工廠方法(例如groupingBy)建立的收集器。java

歸約和彙總

查找流中的最大值和最小值

Collectors.maxBy 和 Collectors.minBy 來計算流中的最大或最小值。數據結構

Optional<Dish> maxDish = Dish.menu.stream().
      collect(Collectors.maxBy(Comparator.comparing(Dish::getCalories)));
maxDish.ifPresent(System.out::println);

Optional<Dish> minDish = Dish.menu.stream().
      collect(Collectors.minBy(Comparator.comparing(Dish::getCalories)));
minDish.ifPresent(System.out::println);

彙總

Collectors.summingInt 彙總求和;
Collectors.averagingInt 彙總求平均值;
Collectors.summarizingInt 彙總全部信息包括數量、求和、平均值、最小值、最大值;ide

//求總熱量
int totalColories = Dish.menu.stream().collect(Collectors.summingInt(Dish::getCalories));
System.out.println(totalColories);

//求平均熱量
double averageColories = Dish.menu.stream().collect(Collectors.averagingInt(Dish::getCalories));
System.out.println(averageColories);

//彙總
IntSummaryStatistics menuStatistics = Dish.menu.stream().collect(Collectors.summarizingInt(Dish::getCalories));
System.out.println(menuStatistics);
IntSummaryStatistics{count=9, sum=4300, min=120, average=477.777778, max=800}

鏈接字符串

joining 工廠方法返回的收集器會把對流中每個對象應用toString方法獲得的全部字符串鏈接成一個字符串。函數

String menu = Dish.menu.stream().map(Dish::getName).collect(Collectors.joining(","));
System.out.println(menu);
//pork,beef,chicken,french fries,rice,season fruit,pizza,prawns,salmon

Collectors.reducing

Collectors.reducing 工廠方法是上面全部工廠方法的通常狀況,它徹底能夠實現上述方法的功能。它須要三個參數:學習

  • 第一個參數是歸約操做的起始值,也是流中沒有元素時的返回值,因此很顯然對於數值和而言0是一個合適的值。ui

  • 第二個參數是一個 Function,就是具體的取值函數。this

  • 第三個參數是一個 BinaryOperator,將兩個項目累積成一個同類型的值。。code

int totalCalories = Dish.menu.stream().collect(Collectors.reducing( 0, Dish::getCalories, (i, j) -> i + j));

分組

用Collectors.groupingBy工廠方法返回的收集器能夠實現分組任務,分組操做的結果是一個Map,把分組函數返回的值做爲映射的鍵,把流中 全部具備這個分類值的項目的列表做爲對應的映射值。對象

多級分組

//Dish的Type爲鍵,Dish類型所對應的dish集合爲值
Map<Dish.Type, List<Dish>> dishesByType = Dish.menu.stream().collect(Collectors.groupingBy(Dish::getType));
System.out.println(dishesByType);
//{FISH=[prawns, salmon], OTHER=[french fries, rice, season fruit, pizza], MEAT=[pork, beef, chicken]}

//一級分類爲Dish的Type,二級分類爲Dish的CaloricLevel
Map<Dish.Type, Map<Dish.CaloricLevel, List<Dish>>> dishes = Dish.menu.stream()
      .collect(Collectors.groupingBy(Dish::getType, Collectors.groupingBy(Dish::getLevel)));
System.out.println(dishes);
//{FISH={NORMAL=[salmon], DIET=[prawns]}, OTHER={NORMAL=[french fries, pizza], DIET=[rice, season fruit]}, MEAT={NORMAL=[beef], FAT=[pork], DIET=[chicken]}}

按子集收集數據

//Dish的Type爲鍵,Dish類型所對應的dish集合的size爲值
Map<Dish.Type, Long> dishTypeCount = Dish.menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.counting()));
System.out.println(dishTypeCount);
//{FISH=2, OTHER=4, MEAT=3}

分區

分區是分組的特殊狀況:由一個謂詞(返回一個布爾值的函數)做爲分類函數,它稱分區函數。分區函數返回一個布爾值,這意味着獲得的分組 Map 的鍵類型是 Boolean,因而它最多能夠分爲兩組——true是一組,false是一組。分區的好處在於保留了分區函數返回true或false的兩套流元素列表。ci

Map<Boolean, Map<Dish.Type, List<Dish>>> partitioningDish = Dish.menu.stream().collect(Collectors.partitioningBy(Dish::isVegetarian, Collectors.groupingBy(Dish::getType)));
System.out.println(partitioningDish);
//false={FISH=[prawns, salmon], MEAT=[pork, beef, chicken]}, 
//true={OTHER=[french fries, rice, season fruit, pizza]}

小結

下表展現 Collectors 類的靜態工廠方法。

工廠方法 返回類型 做用
toList List<T> 把流中全部項目收集到一個 List
toSet Set<T> 把流中全部項目收集到一個 Set,刪除重複項
toCollection Collection<T> 把流中全部項目收集到給定的供應源建立的集合menuStream.collect(toCollection(), ArrayList::new)
counting Long 計算流中元素的個數
sumInt Integer 對流中項目的一個整數屬性求和
averagingInt Double 計算流中項目 Integer 屬性的平均值
summarizingInt IntSummaryStatistics 收集關於流中項目 Integer 屬性的統計值,例如最大、最小、 總和與平均值
joining String 鏈接對流中每一個項目調用 toString 方法所生成的字符串collect(joining(", "))
maxBy Optional<T> 一個包裹了流中按照給定比較器選出的最大元素的 Optional, 或若是流爲空則爲 Optional.empty()
minBy Optional<T> 一個包裹了流中按照給定比較器選出的最小元素的 Optional, 或若是流爲空則爲 Optional.empty()
reducing 歸約操做產生的類型 從一個做爲累加器的初始值開始,利用 BinaryOperator 與流 中的元素逐個結合,從而將流歸約爲單個值累加int totalCalories = menuStream.collect(reducing(0, Dish::getCalories, Integer::sum));
collectingAndThen 轉換函數返回的類型 包裹另外一個收集器,對其結果應用轉換函數int howManyDishes = menuStream.collect(collectingAndThen(toList(), List::size))
groupingBy Map<K, List<T>> 根據項目的一個屬性的值對流中的項目做問組,並將屬性值做 爲結果 Map 的鍵
partitioningBy Map<Boolean,List<T>> 根據對流中每一個項目應用謂詞的結果來對項目進行分區

附錄:Dish類

package com.company.bean;

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

/**
 * Created by liuguoquan on 2017/4/26.
 */
public class Dish {

    private String name;
    private boolean vegetarian;
    private int calories;
    private Type type;
    private CaloricLevel level;

    public CaloricLevel getLevel() {

        if (calories <= 400) {

            return CaloricLevel.DIET;
        } else if (calories <= 700) {

            return CaloricLevel.NORMAL;
        }
        return CaloricLevel.FAT;
    }

    public void setLevel(CaloricLevel level) {
        this.level = level;
    }

    public enum Type { MEAT, FISH, OTHER }
    public enum CaloricLevel { DIET, NORMAL, FAT }

    public Dish(String name, boolean vegetarian, int calories, Type type) {
        this.name = name;
        this.vegetarian = vegetarian;
        this.calories = calories;
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public boolean isVegetarian() {
        return vegetarian;
    }

    public int getCalories() {
        return calories;
    }

    public Type getType() {
        return type;
    }

    @Override
    public String toString() {
        return name;
    }

    public static final List<Dish> menu =
            Arrays.asList( new Dish("pork", false, 800, Dish.Type.MEAT),
                    new Dish("beef", false, 700, Dish.Type.MEAT),
                    new Dish("chicken", false, 400, Dish.Type.MEAT),
                    new Dish("french fries", true, 530, Dish.Type.OTHER),
                    new Dish("rice", true, 350, Dish.Type.OTHER),
                    new Dish("season fruit", true, 120, Dish.Type.OTHER),
                    new Dish("pizza", true, 550, Dish.Type.OTHER),
                    new Dish("prawns", false, 400, Dish.Type.FISH),
                    new Dish("salmon", false, 450, Dish.Type.FISH));
}
相關文章
相關標籤/搜索