1. 程式人生 > >定製歸一化收集器(reducing)得到自定義結果集

定製歸一化收集器(reducing)得到自定義結果集

reducing簡介

reducing 是一個收集器(操作),從字面意義上可以理解為“減少操作”:輸入多個元素,在一定的操作後,元素減少。

reducing 有多個過載方法,其中一個方法如下:

public static <T> Collector<T,?,Optional<T>> reducing(BinaryOperator<T> op)

以上方法,JDK對其的描述是:

Returns a Collector which performs a reduction of its input elements under a specified BinaryOperator. The result is described as an Optional. (返回一個收集器,該收集器在指定的二進位制操作符下執行其輸入元素的減少。結果被描述為可選的 <T>。)

reducing的應用

reducing 是一個非常有用的收集器,可以用在多層流、下游資料分組或分割槽等場合。

下面是一個例子:
給定一組 Person 物件,每個 Person 都有 city(所在城市)和 height(身高)屬性,編寫一個程式,統計出每個不同的城市最大、最小身高值。

import java.util.*;
import java.util.function.BinaryOperator;

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.reducing;

/**
 * ReducingDemo
 *
 * @author Zebe
 */
public class ReducingDemo {

    /**
     * 執行入口
     *
     * @param args 執行引數
     */
    public static void main(String[] args) {
        List<Person> personList = getPersonList(1000000);
        functionStyle(personList);
        normalStyle(personList);
    }

    /**
     * 函數語言程式設計風格(3行處理程式碼)
     * @param personList Person 列表
     */
    private static void functionStyle(List<Person> personList) {
        long start = System.currentTimeMillis();
        // 建立一個比較器,取名為 byHeight (通過高度來比較)
        Comparator<Person> byHeight = Comparator.comparingInt(Person::getHeight);
        // 建立一個歸一收集器
        Map<City, Optional<Person>> tallestByCity = personList.stream().
                collect(groupingBy(Person::getCity, reducing(BinaryOperator.maxBy(byHeight))));
        long usedTime = System.currentTimeMillis() - start;
        printResult("函數語言程式設計風格", personList.size(), usedTime, tallestByCity);
    }

    /**
     * 普通程式設計風格(20行處理程式碼)
     * @param personList Person 列表
     */
    private static void normalStyle(List<Person> personList) {
        long start = System.currentTimeMillis();
        // 建立一個結果集
        Map<City, Optional<Person>> tallestByCity = new HashMap<>();
        // 第一步:找出所有的不同城市
        Set<City> cityList = new HashSet<>();
        for (Person person : personList) {
            if (!cityList.contains(person.getCity())) {
                cityList.add(person.getCity());
            }
        }
        // 第二部,遍歷所有城市,遍歷所有人找出每個城市的最大身高
        for (City city : cityList) {
            int maxHeight = 0;
            Person tempPerson = null;
            for (Person person : personList) {
                if (person.getCity().equals(city)) {
                    if (person.getHeight() > maxHeight) {
                        maxHeight = person.getHeight();
                        tempPerson = person;
                    }
                }
            }
            tallestByCity.put(city, Optional.ofNullable(tempPerson));
        }
        long usedTime = System.currentTimeMillis() - start;
        printResult("普通程式設計風格", personList.size(), usedTime, tallestByCity);
    }

    /**
     * 獲取Person列表
     * @param numbers 要獲取的數量
     * @return 返回指定數量的 Person 列表
     */
    private static List<Person> getPersonList(int numbers) {
        // 建立城市
        final City cityChengDu = new City("成都");
        final City cityNewYork = new City("紐約");
        List<Person> people = new ArrayList<>();
        // 建立指定數量的Person,並指定不同的城市和相對固定的身高值
        for (int i = 0; i < numbers; i++) {
            if (i % 2 == 0) {
                // 成都最大身高185
                people.add(new Person(cityChengDu, 185));
            } else if (i % 3 == 0) {
                people.add(new Person(cityChengDu, 170));
            } else if (i % 5 == 0) {
                // 成都最小身高160
                people.add(new Person(cityChengDu, 160));
            } else if (i % 7 == 0) {
                // 紐約最大身高200
                people.add(new Person(cityNewYork, 200));
            } else if (i % 9 == 0) {
                people.add(new Person(cityNewYork, 185));
            } else if (i % 11 == 0) {
                // 紐約最小身高165
                people.add(new Person(cityNewYork, 165));
            } else {
                // 預設新增紐約最小身高165
                people.add(new Person(cityNewYork, 165));
            }
        }
        return people;
    }

    /**
     * 輸出結果
     * @param styleName 風格名稱
     * @param totalPerson 總人數
     * @param usedTime 計算耗時
     * @param tallestByCity 統計好最大身高的城市分組MAP
     */
    private static void printResult(String styleName, long totalPerson, long usedTime, Map<City, Optional<Person>> tallestByCity) {
        System.out.println("\n" + styleName + ":計算 " + totalPerson + " 個人所在不同城市最大身高的結果如下:(耗時 " + usedTime + " ms)");
        tallestByCity.forEach((city, person) -> {
            person.ifPresent(p -> System.out.println(city.getName() + " -> " + p.getHeight()));
        });
    }

}


Person類

/**
 * Person
 *
 * @author Zebe
 */
public class Person {

    /**
     * 所在城市
     */
    private City city;

    /**
     * 身高
     */
    private int height;

    /**
     * 構造器
     * @param city 所在城市
     * @param height 身高
     */
    public Person(City city, int height) {
        this.city = city;
        this.height = height;
    }

    public City getCity() {
        return city;
    }

    public void setCity(City city) {
        this.city = city;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }
}


City類

/**
 * City
 *
 * @author Zebe
 */
public class City {

    /**
     * 城市名
     */
    private String name;

    /**
     * 構造器
     * @param name 城市名
     */
    public City(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}


注:以上程式碼如果要計算最小值、平均值,將 maxBy 換成 minBy 就可以了。

輸出結果

程式輸出結果如下:

函數語言程式設計風格:計算 1000000 個人所在不同城市最大身高的結果如下:(耗時 149 ms)
成都 -> 185
紐約 -> 200

普通程式設計風格:計算 1000000 個人所在不同城市最大身高的結果如下:(耗時 82 ms)
成都 -> 185
紐約 -> 200


可以看出,函數語言程式設計的效率不一定會比普通程式設計效率更高,甚至相對要慢一點,但是,函數語言程式設計的好處在於:

  • 把引數作為一個函式,而不是值,實現了只有在需要的時候才計算(惰性求值)。
  • 使用 lambda 表示式能夠簡化程式表達的含義,使程式更簡潔明瞭。