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流的常見中間操作方法
這裡涉及函數式接口Predicate接口中的方法:boolean test(T t),對給定的參數進行判斷,傳回一個布爾值
- Stream<T> filter(Predicate predicate):用于對流中的資料進行過濾
Function接口中的方法:R apply(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):傳回由給定函數應用于此流的元素的結果組成的流
DoubleStream :表示原始double流,ToDoubleFunction 接口中的方法:int applyAsDouble(T value)
- DoubleStream mapToDouble(ToDoubleFunction mapper):傳回一個DoubleStream 其中包含将給定函數應用此流的元素結果
- 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流的終結操作
Consumer接口中的方法:void accept(T t),對給定的參數執行此操作
- void forEach(Consumer action):對此流的每個元素執行操作
- <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}