天天看點

Stream流(Stream,Lambda)

Stream流是​Java8​提供的一個新特性,這個有什麼新大陸發現呢,我們先看一個例子

以下内容要先有Lambda表達式基礎,不清楚Lambda表達式的可以看這個

我們以下的例子都是基于這個學生類Student來操作,下面是學生類Student的代碼

學生屬性有:編号,名字,年齡,數學成績,國文成績,重寫toString方法,重寫equals和hashCode方法,編号一樣就是同一個人

package com.TestStream;

/**
 * @author 林高祿
 * @create 2020-06-04-16:47
 */
public class Student {
    private Integer no;
    private String name;
    private Integer age;
    private Double mathScore;
    private Double chineseScore;

    public Student(Integer no, String name, Integer age, Double mathScore, Double chineseScore) {
        this.no = no;
        this.name = name;
        this.age = age;
        this.mathScore = mathScore;
        this.chineseScore = chineseScore;
    }

    public Integer getNo() {
        return no;
    }

    public void setNo(Integer no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Double getMathScore() {
        return mathScore;
    }

    public void setMathScore(Double mathScore) {
        this.mathScore = mathScore;
    }

    public Double getChineseScore() {
        return chineseScore;
    }

    public void setChineseScore(Double chineseScore) {
        this.chineseScore = chineseScore;
    }

    @Override
    public String toString() {
        return "Student{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", mathScore=" + mathScore +
                ", chineseScore=" + chineseScore +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        return no != null ? no.equals(student.no) : student.no == null;
    }

    @Override
    public int hashCode() {
        return no != null ? no.hashCode() : 0;
    }
}      
為了友善代碼的複用,就弄了一個學生的工具類StudentUtil,來生成學生的清單,代碼為
package com.TestStream;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 林高祿
 * @create 2020-06-04-17:18
 */
public class StudentUtil {
    /**
     * 生成指定的學生類的清單,用于測試
     *
     * @return
     */
    public static List<Student> createStudentList() {
        List<Student> studentList = new ArrayList<>();
        studentList.add(new Student(3, "林高祿", 18, 95.5, 68.0));
        studentList.add(new Student(2, "徐輝強", 19, 99.0, 98.0));
        studentList.add(new Student(1, "吳忠威", 23, 86.0, 78.5));
        studentList.add(new Student(17, "東方不敗", 16, 54.0, 47.0));
        studentList.add(new Student(9, "西方失敗", 45, 94.5, 92.0));
        studentList.add(new Student(5, "任我行", 29, 75.0, 97.0));
        studentList.add(new Student(9, "獨孤求敗", 45, 98.5, 86.0));
        return studentList;
    }

    /**
     * 列印學生清單裡的學生資訊
     *
     * @param studentList
     */
    public static void printList(List<Student> studentList) {
        for (Student s : studentList) {
            System.out.println(s);
        }
    }
}      

​篩選出數學成績為90分以上(含90分)的學生,并且分數按從高的到低排序列印​

按照以前的做法,我們的代碼如下

package com.TestStream;

import java.util.*;

/**
 * @author 林高祿
 * @create 2020-06-04-16:51
 */
public class BeforeDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        // 篩選出數學成績為90分以上(含90分)的學生,并且分數按從高的到低排序列印
        System.out.println("-------原資料------");
        StudentUtil.printList(studentList);
        // 1、篩選出數學成績為90分以上(含90分)的學生
        List<Student> newList = new ArrayList<>();
        for(Student s:studentList){
            if(s.getMathScore()>=90){
                newList.add(s);
            }
        }
        System.out.println("-------成績為90分以上(含90分)的資料------");
        StudentUtil.printList(newList);
        //  2、分數按從高的到低排序
        newList.sort(new Comparator<Student>() {
            @Override
            public int compare(Student s1, Student s2) {
                return s2.getMathScore().compareTo(s1.getMathScore());
            }
        });
        // 3、列印
        System.out.println("-------分數按從高的到低的資料------");
        StudentUtil.printList(newList);
    }

}      

輸出:

-------原資料------

Student{no=3, name='林高祿', age=18, mathScore=95.5, chineseScore=68.0}

Student{no=2, name='徐輝強', age=19, mathScore=99.0, chineseScore=98.0}

Student{no=1, name='吳忠威', age=23, mathScore=86.0, chineseScore=78.5}

Student{no=17, name='東方不敗', age=16, mathScore=54.0, chineseScore=47.0}

Student{no=9, name='西方失敗', age=45, mathScore=94.5, chineseScore=92.0}

Student{no=5, name='任我行', age=29, mathScore=75.0, chineseScore=97.0}

Student{no=9, name='獨孤求敗', age=45, mathScore=98.5, chineseScore=86.0}

-------成績為90分以上(含90分)的資料------

Student{no=3, name='林高祿', age=18, mathScore=95.5, chineseScore=68.0}

Student{no=2, name='徐輝強', age=19, mathScore=99.0, chineseScore=98.0}

Student{no=9, name='西方失敗', age=45, mathScore=94.5, chineseScore=92.0}

Student{no=9, name='獨孤求敗', age=45, mathScore=98.5, chineseScore=86.0}

-------分數按從高的到低的資料------

Student{no=2, name='徐輝強', age=19, mathScore=99.0, chineseScore=98.0}

Student{no=9, name='獨孤求敗', age=45, mathScore=98.5, chineseScore=86.0}

Student{no=3, name='林高祿', age=18, mathScore=95.5, chineseScore=68.0}

Student{no=9, name='西方失敗', age=45, mathScore=94.5, chineseScore=92.0}

從這個例子,我們可以看出,這隻是一個小小的需求,我們就要做很多操作,寫很多代碼,要周遊賽選,還要排序,還要周遊列印等等

那麼我們就先體驗一下Stream的寫法(這個看不懂沒關系,這隻是體驗,下面會講到如何使用)

package com.TestStream;

import java.util.*;

/**
 * @author 林高祿
 * @create 2020-06-04-16:51
 */
public class StreamDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        studentList.stream().filter(s -> s.getMathScore() >= 90)
                .sorted((s1, s2) -> s2.getMathScore().compareTo(s1.getMathScore()))
                .forEach(System.out::println);
    }
}      

運作輸出:

Student{no=2, name='徐輝強', age=19, mathScore=99.0, chineseScore=98.0}

Student{no=9, name='獨孤求敗', age=45, mathScore=98.5, chineseScore=86.0}

Student{no=3, name='林高祿', age=18, mathScore=95.5, chineseScore=68.0}

Student{no=9, name='西方失敗', age=45, mathScore=94.5, chineseScore=92.0}

有沒有發現我們的代碼簡潔了很多,并且也很友善,那麼我們下面就講講Stream如何使用

Stream流的使用

  • ​生成流:​通過資料源(集合,數組等)生成流,比如:list.stream()
  • ​中間操作:​一個流後面可以跟随零個或多個中間操作,其目的主要是打開流,做出某種程度的資料過濾、映射,然後傳回一個新的流,交給下一個操作使用,比如:filter()
  • ​終結操作:​一個流隻能有一個終結操作,當這個操作哦執行後,流就被使用“光”了,無法再被操作。是以這必定是流的最後一個操作,比如:forEach()

生成流:

  • Collection體系的集合可以使用預設方法stream()生成流,default Stream<E> stream()
Stream流(Stream,Lambda)
  • Map體系的集合間接的生成流
  • 數組可以通過Stream接口的靜态方法of(T...values)生成流
Stream流(Stream,Lambda)
代碼執行個體
package com.TestStream;

import java.util.*;
import java.util.stream.Stream;

/**
 * @author 林高祿
 * @create 2020-06-05-8:00
 */
public class CreateStreamDemo {
    public static void main(String[] args) {
        //Collection體系的集合可以使用預設方法strean()生成流
        List<Student> studentList = new ArrayList<>();
        Stream<Student> listStream = studentList.stream();

        Set<Student> studentSet = new HashSet<>();
        Stream<Student> seStream = studentSet.stream();

        // Map體系的集合間接的生成流
        Map<String, Student> studentMap = new HashMap<>();
        Stream<String> keyStream = studentMap.keySet().stream();   //.keySet()傳回的是一個set集合
        Stream<Student> valuesStream = studentMap.values().stream();    //.values()傳回的是一個Collection集合
        Stream<Map.Entry<String, Student>> entryStream = studentMap.entrySet().stream();    //.entrySet()傳回的是一個set集合

        // 數組可以通過Stream接口的靜态方法of(T...values)生成流
        Student[] stuArray = {
                new Student(3, "林高祿", 18, 95.5, 68.0),
                new Student(2, "徐輝強", 19, 99.0, 98.0),
                new Student(1, "吳忠威", 23, 86.0, 78.5)
        };
        Stream<Student> stuArrayStream = Stream.of(stuArray);

        // 當然Collection體系也能通過這種方式生成流
        Stream<List<Student>> studentList1 = Stream.of(studentList);
        Stream<Set<Student>> studentSet1 = Stream.of(studentSet);
        // 但是注意,這樣生成的流是List<Student>的,而不是Student的
        studentList1.filter(s -> s.size() > 5);     // 這裡的s指代的就是list,而不是student
    }
}      

Stream流的常見中間操作方法

這裡涉及​​函數式接口​​
  • Stream<T> filter(Predicate predicate):用于對流中的資料進行過濾
        Predicate接口中的方法:boolean test(T t),對給定的參數進行判斷,傳回一個布爾值
  • Stream<T> limit(long maxSize):傳回此流中元素組成的流,截取前指定參數個數的資料組成的流
  • Stream<T> skip(long n):跳過指定參數個數的資料,傳回由該流的剩餘元素組成的流
  • static<T> Stream<T> concat(Stream a,Stream b):合并a和b兩個流為一個流
  • Stream<T> distinct():傳回由該流的不同元素(根據Object.equals(Object)和hashCode())組成的流,去重
  • Stream<T> soeted()傳回由此流的元素組成的流,根據自然順序排序
  • Stream<T> soeted(Comparator comparator):傳回傳回由此流的元素組成的流,根據提供的Comparator進行排序
  • <R> Stream<R> map(Function mapper):傳回由給定函數應用于此流的元素的結果組成的流
         Function接口中的方法:R apply(T t)
  • DoubleStream mapToDouble(ToDoubleFunction mapper):傳回一個DoubleStream 其中包含将給定函數應用此流的元素結果
         DoubleStream :表示原始double流,ToDoubleFunction 接口中的方法:int applyAsDouble(T value)
  • Stream<T> filter(Predicate predicate):用于對流中的資料進行過濾

        Predicate接口中的方法:boolean test(T t),對給定的參數進行判斷,傳回一個布爾值

例子:賽選出國文成績大于等于90分的學生,并且列印出來

package com.TestStream;

import java.util.List;

/**
 * @author 林高祿
 * @create 2020-06-05-9:50
 */
public class FilterDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        studentList.stream().filter(s -> s.getChineseScore() >= 90).forEach(System.out::println);
    }
}      

運作輸出:

Student{no=2, name='徐輝強', age=19, mathScore=99.0, chineseScore=98.0}

Student{no=9, name='西方失敗', age=45, mathScore=94.5, chineseScore=92.0}

Student{no=5, name='任我行', age=29, mathScore=75.0, chineseScore=97.0}

Stream<T> limit(long maxSize):傳回此流中元素組成的流,截取前指定參數個數的資料組成的流

例子:輸出清單中前2個學生資訊
package com.TestStream;

import java.util.List;

/**
 * @author 林高祿
 * @create 2020-06-05-10:05
 */
public class LimitDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        studentList.stream().limit(2).forEach(System.out::println);
    }
}      

運作輸出:

Student{no=3, name='林高祿', age=18, mathScore=95.5, chineseScore=68.0}

Student{no=2, name='徐輝強', age=19, mathScore=99.0, chineseScore=98.0}

Stream<T> skip(long n):跳過指定參數個數的資料,傳回由該流的剩餘元素組成的流

例子:輸出清單中最後2個學生資訊
package com.TestStream;

import java.util.List;

/**
 * @author 林高祿
 * @create 2020-06-05-10:05
 */
public class SkipDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        studentList.stream().skip(studentList.size() - 2).forEach(System.out::println);
    }
}      

運作輸出:

Student{no=5, name='任我行', age=29, mathScore=75.0, chineseScore=97.0}

Student{no=9, name='獨孤求敗', age=45, mathScore=98.5, chineseScore=86.0}

static<T> Stream<T> concat(Stream a,Stream b):合并a和b兩個流為一個流

例子:将學生清單中的前4名的資訊記錄,再把後5名的資訊記錄,列印這些全部記錄資訊(我們的清單隻有7個學生)
package com.TestStream;

import java.util.List;
import java.util.stream.Stream;

/**
 * @author 林高祿
 * @create 2020-06-05-10:18
 */
public class ConcatDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        Stream<Student> limitStream = studentList.stream().limit(4);
        Stream<Student> skipStream = studentList.stream().skip(studentList.size() - 5);
        Stream.concat(limitStream, skipStream).forEach(System.out::println);
    }
}      

運作輸出:

Student{no=3, name='林高祿', age=18, mathScore=95.5, chineseScore=68.0}

Student{no=2, name='徐輝強', age=19, mathScore=99.0, chineseScore=98.0}

Student{no=1, name='吳忠威', age=23, mathScore=86.0, chineseScore=78.5}

Student{no=17, name='東方不敗', age=16, mathScore=54.0, chineseScore=47.0}

Student{no=1, name='吳忠威', age=23, mathScore=86.0, chineseScore=78.5}

Student{no=17, name='東方不敗', age=16, mathScore=54.0, chineseScore=47.0}

Student{no=9, name='西方失敗', age=45, mathScore=94.5, chineseScore=92.0}

Student{no=5, name='任我行', age=29, mathScore=75.0, chineseScore=97.0}

Student{no=9, name='獨孤求敗', age=45, mathScore=98.5, chineseScore=86.0}

發現沒有,​吳忠威和東方不敗​這2個學生是重複的資訊,concat合并流的時候并沒有去重,那麼我們下面就來看一個去重的方法

Stream<T> distinct():傳回由該流的不同元素(根據Object.equals(Object))組成的流,去重

例子:将學生清單中的前4名的調去新班,再把後5名也調去新班,列印新班的學生信心(我們的清單隻有7個學生)
package com.TestStream;

import java.util.List;
import java.util.stream.Stream;

/**
 * @author 林高祿
 * @create 2020-06-05-10:18
 */
public class DistinctDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        Stream<Student> limitStream = studentList.stream().limit(4);
        Stream<Student> skipStream = studentList.stream().skip(studentList.size() - 5);
        Stream.concat(limitStream, skipStream).distinct().forEach(System.out::println);
    }
}      

運作輸出:

Student{no=3, name='林高祿', age=18, mathScore=95.5, chineseScore=68.0}

Student{no=2, name='徐輝強', age=19, mathScore=99.0, chineseScore=98.0}

Student{no=1, name='吳忠威', age=23, mathScore=86.0, chineseScore=78.5}

Student{no=17, name='東方不敗', age=16, mathScore=54.0, chineseScore=47.0}

Student{no=9, name='西方失敗', age=45, mathScore=94.5, chineseScore=92.0}

Student{no=5, name='任我行', age=29, mathScore=75.0, chineseScore=97.0}

去重之後,合并的9條資料中,​吳忠威和東方不敗​這2個學生是重複的資訊去掉了,但是我們發現​獨孤求敗​這個學生的資訊也不見了,這是因為我們的學生類重寫了equals和hashCode的方法,編号一樣的學生就為同一個對象,而​獨孤求敗​這個學生的編号我9,編号9已被學生​西方失敗​使用了,是以判定​獨孤求敗​這個學生重複了,是以去掉了。

Stream<T> soeted()傳回由此流的元素組成的流,根據自然順序排序

例子:将學生按自然順序排序
package com.TestStream;

import java.util.List;

/**
 * @author 林高祿
 * @create 2020-06-05-10:18
 */
public class SortDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        studentList.stream().sorted().forEach(System.out::println);
    }
}      

運作輸出:

Exception in thread "main" java.lang.ClassCastException: com.TestStream.Student cannot be cast to java.lang.Comparable

    at java.util.Comparators$NaturalOrderComparator.compare(Comparators.java:47)

    at java.util.TimSort.countRunAndMakeAscending(TimSort.java:355)

    at java.util.TimSort.sort(TimSort.java:220)

    at java.util.Arrays.sort(Arrays.java:1512)

    at java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:348)

    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)

    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)

    at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)

    at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)

    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)

    at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)

    at com.TestStream.SortDemo.main(SortDemo.java:12)

報錯了,為何,按自然排序到底是按什麼排序啊?因為我們的學生類沒有實作​Comparable​接口,沒有重寫排序方法,是以報錯,是以我們就用另一個排序的方法,指定的Comparator進行排序,往下看

Stream<T> soeted(Comparator comparator):傳回傳回由此流的元素組成的流,根據提供的Comparator進行排序

例子:将學生按編号排序輸出
package com.TestStream;

import java.util.Comparator;
import java.util.List;

/**
 * @author 林高祿
 * @create 2020-06-05-10:18
 */
public class SortDemo2 {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        studentList.stream().sorted(Comparator.comparing(Student::getNo)).forEach(System.out::println);
    }
}      

運作輸出:

Student{no=1, name='吳忠威', age=23, mathScore=86.0, chineseScore=78.5}

Student{no=2, name='徐輝強', age=19, mathScore=99.0, chineseScore=98.0}

Student{no=3, name='林高祿', age=18, mathScore=95.5, chineseScore=68.0}

Student{no=5, name='任我行', age=29, mathScore=75.0, chineseScore=97.0}

Student{no=9, name='西方失敗', age=45, mathScore=94.5, chineseScore=92.0}

Student{no=9, name='獨孤求敗', age=45, mathScore=98.5, chineseScore=86.0}

Student{no=17, name='東方不敗', age=16, mathScore=54.0, chineseScore=47.0}

  • <R> Stream<R> map(Function mapper):傳回由給定函數應用于此流的元素的結果組成的流

         Function接口中的方法:R apply(T t)

例子1:将學生的姓名和數學分數輸出:

package com.TestStream;

import java.util.List;

/**
 * @author 林高祿
 * @create 2020-06-05-10:18
 */
public class MapDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        studentList.stream().map(s -> s.getName() + s.getMathScore()).forEach(System.out::println);
    }
}      

運作輸出:

林高祿95.5

徐輝強99.0

吳忠威86.0

東方不敗54.0

西方失敗94.5

任我行75.0

獨孤求敗98.5

  • DoubleStream mapToDouble(ToDoubleFunction mapper):傳回一個DoubleStream 其中包含将給定函數應用此流的元素結果

         DoubleStream :表示原始double流,ToDoubleFunction 接口中的方法:int applyAsDouble(T value)

 例子:求出所有人的數學成績的總和

package com.TestStream;

import java.util.List;

/**
 * @author 林高祿
 * @create 2020-06-05-10:18
 */
public class MapToDoubleDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        double sum = studentList.stream().mapToDouble(Student::getMathScore).sum();
        System.out.println(sum);

    }
}      

運作輸出:

602.5

​mapToInt​和​mapToLong​也是和​mapToDouble​一樣的運用。

​Stream流的終結操作​

  • void forEach(Consumer action):對此流的每個元素執行操作
        Consumer接口中的方法:void accept(T t),對給定的參數執行此操作
  • <R, A> R collect(Collector<? super T, A, R> collector),收集流為指定的集合類型
  • long count():傳回此流中的元素個數
  • void forEach(Consumer action):對此流的每個元素執行操作

        Consumer接口中的方法:void accept(T t),對給定的參數執行此操作

這個我們上面的例子一直再使用。用于輸出學生的資訊,就不再舉例

<R, A> R collect(Collector<? super T, A, R> collector),收集流為指定的集合類型

 例子:取出前3名學生為一個新的集合,并且列印
package com.TestStream;

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

/**
 * @author 林高祿
 * @create 2020-06-05-10:18
 */
public class CollectDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        List<Student> collect = studentList.stream().limit(3).collect(Collectors.toList());
        StudentUtil.printList(collect);
    }
}      

運作輸出:

Student{no=3, name='林高祿', age=18, mathScore=95.5, chineseScore=68.0}

Student{no=2, name='徐輝強', age=19, mathScore=99.0, chineseScore=98.0}

Student{no=1, name='吳忠威', age=23, mathScore=86.0, chineseScore=78.5}

long count():傳回此流中的元素個數

例子:統計國文分數大于等于90分的學生的個數
package com.TestStream;

import java.util.List;

/**
 * @author 林高祿
 * @create 2020-06-05-10:18
 */
public class CountDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        long count = studentList.stream().filter(s -> s.getChineseScore() >= 90).count();
        System.out.println(count);
    }
}      

運作輸出:

3

到這裡,Stream基本上就介紹完了,我們做一個綜合的例子回顧一下

例子:列印出總分排名第三的學生資訊

思路有很多,我的思路:

A:1、進行總分倒序排名;2、去掉前面2個;3、取前2步得出的第一個;4、列印

B:1、進行總分倒序排名;2、取前3個;3、取前2步得出的最後一個;4、列印

package com.TestStream;

import java.util.List;

/**
 * @author 林高祿
 * @create 2020-06-05-10:18
 */
public class AllDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        System.out.println("------思路A------");
        studentList.stream().sorted((s1,s2)->new Double(s2.getMathScore()+s2.getChineseScore()).compareTo(new Double(s1.getMathScore()+s1.getChineseScore())))
                .skip((3-1)).limit(1).forEach(System.out::println);
        System.out.println("------分割線------");
        System.out.println("------思路B------");
        studentList.stream().sorted((s1,s2)->new Double(s2.getMathScore()+s2.getChineseScore()).compareTo(new Double(s1.getMathScore()+s1.getChineseScore())))
               .limit(3).skip((3-1)).forEach(System.out::println);
    }
}      

運作輸出:

------思路A------

Student{no=9, name='獨孤求敗', age=45, mathScore=98.5, chineseScore=86.0}

------分割線------

------思路B------

Student{no=9, name='獨孤求敗', age=45, mathScore=98.5, chineseScore=86.0}