Java8 新特性之流式數據處理

1、流式處理簡介

在我接觸到java8流式處理的時候,個人第一感受是流式處理讓集合操做變得簡潔了許多,一般咱們須要多行代碼才能完成的操做,藉助於流式處理能夠在一行中實現。好比咱們但願對一個包含整數的集合中篩選出全部的偶數,並將其封裝成爲一個新的List返回,那麼在java8以前,咱們須要經過以下代碼實現:java

List<Integer> evens = new ArrayList<>();
	for (Integer num : nums){
		if (num %2 == 0){
		evens.add(num);
		}
}

經過java8的流式處理,咱們能夠將代碼簡化爲:數組

List<Integer> evens = nums.stream().filter(num -> num %2 == 0).collect(Collectors.toList());

先簡單解釋一下上面這行語句的含義,stream()操做將集合轉換成一個流,filter執行咱們自定義的篩選處理,這裏是經過lambda表達式篩選出全部偶數,最後咱們經過collect()對結果進行封裝處理,並經過Clectors.toList()指定其封裝成爲一個List集合返回。安全

由上面的例子能夠看出,java8的流式處理極大的簡化了對於集合的操做,實際上不光是集合,包括數組、文件等,只要是能夠轉換成流,咱們均可以藉助流式處理,相似於咱們寫SQL語句同樣對其進行操做。java8經過內部迭代來實現對流的處理,一個流式處理能夠分爲三個部分:轉換成流、中間操做、終端操做。以下圖: app

以集合爲例,一個流式處理的操做咱們首先須要調用stream()函數將其轉換成流,而後再調用相應的中間操做達到咱們須要對集合進行的操做,好比篩選、轉換等,最後經過終端操做對前面的結果進行封裝,返回咱們須要的形式。jvm

2、中間操做

咱們定義一個簡單的學生實體類,用於後面的例子演示:函數

public class Student {

    /** 學號 */
    private long id;

    private String name;

    private int age;

    /** 年級 */
    private int grade;

    /** 專業 */
    private String major;

    /** 學校 */
    private String school;

    // 省略getter和setter
}
// 初始化
List<Student> students = new ArrayList<Student>() {
    {
        add(new Student(20160001, "孔明", 20, 1, "土木工程", "武漢大學"));
        add(new Student(20160002, "伯約", 21, 2, "信息安全", "武漢大學"));
        add(new Student(20160003, "玄德", 22, 3, "經濟管理", "武漢大學"));
        add(new Student(20160004, "雲長", 21, 2, "信息安全", "武漢大學"));
        add(new Student(20161001, "翼德", 21, 2, "機械與自動化", "華中科技大學"));
        add(new Student(20161002, "元直", 23, 4, "土木工程", "華中科技大學"));
        add(new Student(20161003, "奉孝", 23, 4, "計算機科學", "華中科技大學"));
        add(new Student(20162001, "仲謀", 22, 3, "土木工程", "浙江大學"));
        add(new Student(20162002, "魯肅", 23, 4, "計算機科學", "浙江大學"));
        add(new Student(20163001, "丁奉", 24, 5, "土木工程", "南京大學"));
    }
};

2.1過濾

過濾,顧名思義就是按照給定的要求對集合進行篩選知足條件的元素,java8提供的篩選操做包括:filter、distinct、limit、skip。性能

2.1.1 filter

在前面的例子中咱們已經演示瞭如何使用filter,其定義爲:Stream<T> filter(Predicate<? super T> predicate),filter接受一個謂詞Predicate,咱們能夠經過這個謂詞定義篩選條件,在介紹lambda表達式時咱們介紹過Predicate是一個函數式接口,其包含一個test(T t)方法,該方法返回boolean。如今咱們但願從集合students中篩選出全部武漢大學的學生,那麼咱們能夠經過filter來實現,並將篩選操做做爲參數傳遞給filter:設計

List<Student> whStudents = students.stream().filter(student -> "武漢大學".equals(student.getSchool())).collect(Collectors.toList());

2.1.2 distinct

distinct操做相似於咱們在寫SQL語句時,添加的DISTINCT關鍵字,用於去重處理,distinct基於Object。equals(Object)實現,回到最開始的例子,假設咱們但願篩選出全部不重複的偶數,那麼能夠添加distinct操做:code

List<Integer> evens = nums.stream()
                        .filter(num -> num % 2 == 0).distinct()
                        .collect(Collectors.toList());

2.1.3 limit

limit操做也相似於SQL語句中的LIMIT關鍵字,不過相對功能較弱,limit返回包含前n個元素的流,當集合大小小於n時,則返回實際長度,好比下面的例子返回前兩個專業爲土木工程專業的學生:blog

List<Student> whStudents = students.stream().filter(student -> "土木工程".equals(student.getMajor())).limit(2)
                .collect(Collectors.toList());

說到limit,不得不說起一下另一個流操做:sorted。該操做用於對流中元素進行排序,sorted要求待比較的元素必須實現Comparable接口,若是沒有實現也沒關係,咱們能夠將比較器做爲參數傳遞給sorted(Comparatoe<? super T> comparator),好比咱們但願篩選出專業爲土木工程的學生,並按年齡從小到大排序,篩選出年齡最小的兩個學生,那麼能夠實現爲

List<Student> whStudents = students.stream().filter(student -> "土木工程".equals(student.getMajor()))
                .sorted((s1,s2) -> s1.getAge()-s2.getAge())
                .limit(2)
                .collect(Collectors.toList());

2.1.4 skip

skip操做與limit操做相反,如同其字面意思同樣,是跳過前n個元素,好比咱們但願找出排序在2以後的土木工程專業的學生,那麼能夠實現爲:

List<Student> whStudents = students.stream().filter(student -> "土木工程".equals(student.getMajor()))
                .skip(2)
                .collect(Collectors.toList());

經過skip,就會跳過前面兩個元素,返回有後面全部元素構造的流,若是n大於知足條件的集合的長度,則會返回一個空的集合。

2.2 映射

在SQL中,藉助SELECT關鍵字後面添加須要的字段名稱,能夠僅輸出咱們須要的字段數據,而流式處理的映射操做也是實現這一目的,在java8的流式處理中,主要包含兩類映射操做:map和flatMap。

2.2.1 map

舉例說明,假設咱們但願篩選出全部專業爲計算機科學的學生姓名,那麼咱們能夠在filter篩選的基礎之上,經過map將學生實體映射成爲學生姓名字符串,具體實現以下:

List<String> names = students.stream().filter(student -> "土木工程".equals(student.getMajor()))
                .map(Student::getName).collect(Collectors.toList());

除了上面這類基礎的map,java8還提供了mapToDouble(ToDoubleFunction<? super T> mapper), mapToInt(ToIntFunction<? super T> mapper), mapToLong(ToLongFunction<? super T> mapper), 這些映射分別返回對應類型的流,java8爲這麼流設定了一些特殊的操做,好比咱們但願計算全部專業爲計算機科學學生的年齡之和,那麼咱們能夠實現以下:

int totalAge = students.stream().filter(student -> "計算機科學".equals(student.getMajor()))
                .mapToInt(Student::getAge).sum();

經過將Student按照年齡直接映射爲IntStream,咱們能夠直接調用提供的sum()方法來達到目的,此外使用這些數值流的好處還在於避免jvm裝箱操做所帶來的性能消耗。

2.2.2 flatMap

3、終端操做

終端操做是流式處理的最後一步,咱們能夠在終端操做中實現對流查找、歸約等操做。

3.1 查找

3.1.1 allMatch

allMatch用於檢測是否所有都知足指定的參數行爲,若是所有知足則返回true,假如咱們但願檢測是否全部的學生都已滿18週歲,那麼能夠實現爲:

boolean isAdult = students.stream().allMatch(student -> student.getAge() >= 18);

3.1.2 anyMatch

anyMatch則是檢測是否存在一個或多個知足指定的參數行爲,若是知足則返回true,假如咱們但願檢測是否有來自武漢大學的學生,那麼能夠實現爲:

boolean isAdult = students.stream().anyMatch(student -> "武漢大學".equals(student.getSchool()));

3.1.3 noneMatch

noneMatch用於檢測是否不存在知足執行行爲的元素,若是不存在則返回true,假如咱們但願檢測是否不存在專業爲計算機科學的學生,能夠實現以下:

boolean isAdult = students.stream().noneMatch(student -> "計算機科學".equals(student.getMajor()));

3.1.4 findFirst

findFirst用於返回知足條件的第一個元素,好比咱們但願選出專業爲土木工程的排在第一個學生,那麼能夠實現以下:

Optional<Student> optStudent = students.stream().filter(student -> "土木工程".equals(student.getMajor())).findFirst();

findFirst不攜帶參數,具體的查找條件能夠經過filter設置,此外咱們能夠發現findFirst返回的是一個Optional類型,關於該類型的具體講解能夠參考上一篇:Java8新特性-Optional類。

3.1.5 findAny

findAny相對於findFirst的區別在於,findAny不必定返回第一個,而是返回任意一個,好比咱們但願返回任意一個專業爲土木工程的學生,能夠實現以下:

Optional<Student> optStudent = students.stream().filter(student -> "土木工程".equals(student.getMajor())).findAny();

實際上對於順序流式處理而言,findFirst和findAny返回的結果是同樣的,至於爲何會這樣設計,是由於在下一篇咱們介紹的並行流式處理,當咱們啓用並行流式處理的時候,查找第一個元素每每會有不少限制,若是不是特別需求,在並行流式處理中使用findAny的性能要比findFirst好。

3.2 歸約

前面的例子中咱們大部分都是經過collect(Collectors.toList())對數據封裝返回,如咱們的目標不是返回一個新的集合,而是但願對通過參數化操做後的集合進行進一步的運算,那麼咱們可用對集合實施歸約的操做。java8的流式處理提供了reduce方法來達到這一目的。

前面咱們經過mapToInt將Stream<Student>映射成爲IntStream,並經過IntStream的sum方法求得全部學生的年齡之和,實際上咱們經過歸約操做,也能夠達到這一目的,實現以下:

// 前面例子中的方法
int totalAge = students.stream()
                .filter(student -> "計算機科學".equals(student.getMajor()))
                .mapToInt(Student::getAge).sum();
// 歸約操做
int totalAge = students.stream()
                .filter(student -> "計算機科學".equals(student.getMajor()))
                .map(Student::getAge)
                .reduce(0, (a, b) -> a + b);

// 進一步簡化
int totalAge2 = students.stream()
                .filter(student -> "計算機科學".equals(student.getMajor()))
                .map(Student::getAge)
                .reduce(0, Integer::sum);

// 採用無初始值的重載版本,須要注意返回Optional
Optional<Integer> totalAge = students.stream()
                .filter(student -> "計算機科學".equals(student.getMajor()))
                .map(Student::getAge)
                .reduce(Integer::sum);  // 去掉初始值

3.3 收集

前面利用collect(Collectors.toList())是一個簡單的收集操做,是對處理結果的封裝,對應的還有toSet、toMap,以知足咱們對於結果組織的需求。這些方法均來自於java.util.stream.Collectors,咱們能夠稱之爲收集器。

3.3.1 概括

收集器也提供了相應的概括操做,可是與reduce在內部實現上是有區別的,收集器更加適用於可變容器的概括操做,這些收集器廣義上均基於Collectors.reducing()實現。

例1:求學生的總人數

long count = students.stream().collect(Collectors.counting());

//進一步簡化
long count2 = students.stream().count();

例2:求年齡的最大值和最小值

//求最大年齡
Optional<Student> opStudent = students.stream().collect(Collectors.maxBy((s1,s2) -> s1.getAge()-s2.getAge()));

//進一步簡化
Optional<Student> opStudent2 = students.stream().collect(Collectors.maxBy(Comparator.comparing(Student::getAge)));
//求最小年齡
Optional<Student> opStudent3 = students.stream().collect(Collectors.minBy(Comparator.comparing(Student::getAge)));

例3:求年齡總和

int totalAge = students.stream().collect(Collectors.summingInt(Student::getAge));

對應的還有summingLong、summingDouble。

例4:求年齡的平均值

相關文章
相關標籤/搜索