因?yàn)閷W(xué)習(xí)了Lambda表達(dá)式,再學(xué)習(xí)Java8新特性之Stream的API常用函數(shù)使用及說明,兩者結(jié)合起來,代碼就可以玩得更溜,下面做Stream的一些常用函數(shù)使用及說明,方日后溫習(xí),如果發(fā)現(xiàn)有不足之處,請(qǐng)多多指教。
什么是Stream(流)?
Stream是數(shù)據(jù)渠道,用于操作數(shù)據(jù)源(如:集合,數(shù)組等)轉(zhuǎn)成的元素序列,對(duì)集合中的每個(gè)元素進(jìn)行一系列并行或者串行的流水線操作。
Stream的作用是什么(為什么需要Stream)?
Stream是Java8的一大亮點(diǎn),它與java.io包中的InputStream、OutputStream是完全不同的概念;也不同于解析XML出來的Stream。
Stream是java對(duì)集合對(duì)象功能的增強(qiáng),對(duì)集合對(duì)象進(jìn)行各種非常方便、高效的聚合操作,或者對(duì)大指數(shù)據(jù)操作。Stream的API同樣借助于Lambda表達(dá)式(支持函數(shù)式編程,代碼非常簡潔),提高編程效率和程序的可讀性,同時(shí)它還提供了并行和串行這兩種模式進(jìn)行匯聚操。默認(rèn)情況能充分利用cpu的資源(Stream操作是延遲執(zhí)行的,需要結(jié)果的時(shí)候才執(zhí)行)。
Stream操作的三步曲
- 創(chuàng)建Stream:通過一個(gè)數(shù)據(jù)源(如:數(shù)組,集合),獲到一個(gè)流。
- 中間操作:一個(gè)中間操作鏈(如:filter,map等),對(duì)數(shù)據(jù)源的數(shù)據(jù)進(jìn)行處理。
- 終止操作:一個(gè)終止操作,執(zhí)行中間操作鏈,并產(chǎn)生結(jié)果。
Stream創(chuàng)建的四種方式
-
Collection提供了兩個(gè)方法,分別為stream()和parallelStream()。
//通過List集合創(chuàng)建Stream List<String> list = new ArrayList<>(); Stream<String> stream1 = list.stream();//獲取一個(gè)順序流 Stream<String> parallelStream = list.parallelStream(); //獲取一個(gè)并行流
-
通過Arrays中的靜態(tài)方法stream()獲取一個(gè)數(shù)組流。
//通過Arrays中的靜態(tài)方法stream()獲取一個(gè)數(shù)組流 Student[] students = new Student[2]; Stream<Student> stream = Arrays.stream(students);
-
通過Stream類中的靜態(tài)方法of()。
注意:這里可以是字符串,數(shù)組,集合等其他數(shù)據(jù)類型。
//通過Stream類中的靜態(tài)方法of()。注意:這里可以是字符串,數(shù)組,集合等其他數(shù)據(jù)類型。 Stream<String> stream3 = Stream.of("java", "lambda", "stream", "6666");
-
創(chuàng)建無限流。
//使用iterate()創(chuàng)建無限流,這個(gè)通常和limit()一起使用,限制流中元素的個(gè)數(shù)。 //iterate()接受一個(gè)種子值,和一個(gè)UnaryOperator(例如 f)。然后種子值成為Stream的第一個(gè)元素, //f(seed) 為第二個(gè),f(f(seed)) 第三個(gè),以此類推. Stream<Integer> iterate = Stream.iterate(0, x -> x + 1); iterate.limit(5).forEach(System.out::println); //使用generate()創(chuàng)建無限流,通常跟limit()一起使用,限制流中元素的個(gè)數(shù)。 //可以根據(jù)任何計(jì)算方式來生成,通過實(shí)現(xiàn)Supplier接口,你可以自己來控制流的生成。 Stream<Long> generate = Stream.generate(new Supplier<Long>() { long a = 1, b = 2; @Override public Long get() { long temp = a + b; a = b; b = temp; return a; } }); generate.limit(10).forEach(System.out::println);
Stream的中間操作
中間操作符(如:filter,map等),多個(gè)中間操作可以連接起來形成一條流水線形式對(duì)數(shù)據(jù)源的數(shù)據(jù)進(jìn)行處理。注意:如果未觸發(fā)終止操作
(下面會(huì)進(jìn)行介紹),中間操作是不會(huì)執(zhí)行任何處理,在觸發(fā)終止操作時(shí)會(huì)一次性全部執(zhí)行中間操作鏈并產(chǎn)生結(jié)果。
中間操作有狀態(tài)之分:無狀態(tài)——指元素的處理不受之前元素的影響;有狀態(tài)——指該操作只有拿到所有元素之后才能繼續(xù)下去。
Stream的終止操作(結(jié)束操作)
一個(gè)終止操作,執(zhí)行中間操作鏈,并產(chǎn)生結(jié)果。終止操作有狀態(tài)之分:非短路——指必須處理所有元素才能得到最終結(jié)果;短路——指遇到某些符合條件的元素就可以得到最終結(jié)果,如 A || B,只要A為true,則無需判斷B的結(jié)果。
Stream的實(shí)際操作
我們了解了Java8新特性Stream的API后,開始以代碼實(shí)踐操作一遍。
一、中間操作就是對(duì)容器(集合)的處理過程,包括:篩選(filter、limit、distinct、skip...),映射(map、flatMap...),排序(sorted...),消費(fèi)(peek..)等 。先寫一個(gè)學(xué)生科目分?jǐn)?shù)對(duì)象,以便操作。
public class StudentSubject {
private String studentName;
private String subject;
private Integer score;
public StudentSubject(String studentName, String subject, Integer score) {
this.studentName = studentName;
this.subject = subject;
this.score = score;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
@Override
public String toString() {
return "StudentSubject{" +
"studentName='" + studentName + '\'' +
", subject='" + subject + '\'' +
", score=" + score +
'}';
}
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
final StudentSubject ss = (StudentSubject) obj;
if (this == ss) {
return true;
} else {
return (this.studentName.equals(ss.studentName));
}
}
@Override
public int hashCode() {
int hash_code = 7;
hash_code = 13 * hash_code+ (studentName == null ? 0 : studentName.hashCode());
return hash_code;
}
}
注意:因?yàn)橹虚g操作鏈在沒有執(zhí)行終止操作前是不會(huì)執(zhí)行的,所有下面都是以執(zhí)行forEach終止操作來驗(yàn)證中間操作鏈的使用。
篩選(filter、limit、distinct、skip...)
-
filter(),過濾掉某些元素。如:獲取大90的科目信息
StudentSubject s1 = new StudentSubject("張三", "語文", 85); StudentSubject s2 = new StudentSubject("小李", "語文", 91); StudentSubject s3 = new StudentSubject("張三", "數(shù)學(xué)", 95); StudentSubject s4 = new StudentSubject("小李", "數(shù)學(xué)", 95); StudentSubject s5 = new StudentSubject("張三", "英語", 92); List<StudentSubject> list = new ArrayList<>(); list.add(s1); list.add(s2); list.add(s3); list.add(s4); list.add(s5); //獲取大90的科目信息 list.stream().filter(item->item.getScore()>90).forEach(System.out::println); //輸出的結(jié)果為 StudentSubject{studentName='小李', subject='語文', score=91} StudentSubject{studentName='張三', subject='數(shù)學(xué)', score=95} StudentSubject{studentName='小李', subject='數(shù)學(xué)', score=95} StudentSubject{studentName='張三', subject='英語', score=92}
-
limit(),截?cái)嗔鳎蛊湓夭怀^給定的數(shù)量。
//截流,取前面三個(gè)元素信息 list.stream().limit(3).forEach(System.out::println); //打印結(jié)果 StudentSubject{studentName='張三', subject='語文', score=85} StudentSubject{studentName='小李', subject='語文', score=91} StudentSubject{studentName='張三', subject='數(shù)學(xué)', score=95}
-
distinct(),通過流所生成的元素的hashCode()和equals()去除重復(fù)元素。
注意:這里有坑,要讓distinct起作用,就必須在對(duì)應(yīng)實(shí)體類中重寫HashCode和equals方法。
//通過流所生成的元素的hashCode()和equals()去除重復(fù)元素。 list.stream().distinct().forEach(System.out::println); //打印結(jié)果 StudentSubject{studentName='張三', subject='語文', score=85} StudentSubject{studentName='小李', subject='語文', score=91} List<String> stringList = Arrays.asList("aa","bb","cc","aa","bb","cc"); stringList.stream().distinct().forEach(System.out::println); //打印結(jié)果 aa bb cc
-
skip(), 跳過元素,返回一個(gè)扔掉了前N個(gè)元素的流。 如果流中元素N個(gè),則是返回一個(gè)空流。與limit(n)互補(bǔ)。
//扔掉前面三個(gè)元素 list.stream().skip(3).forEach(System.out::println); //打印結(jié)果 StudentSubject{studentName='小李', subject='數(shù)學(xué)', score=95} StudentSubject{studentName='張三', subject='英語', score=92}
映射(map、flatMap...)
-
map(),接收一個(gè)函數(shù)作為參數(shù),該函數(shù)會(huì)被應(yīng)用到每一個(gè)元素上,并將其他映射成一個(gè)新的元素。
//>=95分的就輸入科目信息,否則就為null list.stream().map(item->{ if(item.getScore()>=95){ return item; } return null; }).forEach(System.out::println); //打印結(jié)果 null null StudentSubject{studentName='張三', subject='數(shù)學(xué)', score=95} StudentSubject{studentName='小李', subject='數(shù)學(xué)', score=95} null //>=95分的就輸入true,否則就為false list.stream().map(item->item.getScore()>=95).forEach(System.out::println); //打印結(jié)果 false false true true false //寫法2 涉及到collect終止操作,下面會(huì)詳細(xì)說。 //>=95分的就輸入科目信息,否則就為null List<StudentSubject> studentSubjectStream = list.stream().map(item -> { if (item.getScore() >= 95) { return item; } return null; }).collect(Collectors.toList()); studentSubjectStream.forEach(System.out::println); //>=95分的就輸入true,否則就為false List<Boolean> booleanStream = list.stream().map(item -> item.getScore() >= 95) .collect(Collectors.toList()); booleanStream.forEach(System.out::println);
-
mapToInt
//mapToInt 跟map都類似的,只是類型被限定為IntStream。 IntSummaryStatistics intSummaryStatistics = list.stream().mapToInt(person-> person.getScore()).summaryStatistics(); System.out.println("最大分?jǐn)?shù):"+intSummaryStatistics.getMax()); //最大值 System.out.println("最小分?jǐn)?shù):"+intSummaryStatistics.getMin()); //最小值 System.out.println("分?jǐn)?shù)總和:"+intSummaryStatistics.getSum()); //總計(jì) System.out.println("信息條數(shù):"+intSummaryStatistics.getCount());//個(gè)數(shù) System.out.println("平均分?jǐn)?shù):"+intSummaryStatistics.getAverage());//平均數(shù)返回的是double類型
mapToLong、mapToDouble 跟map都類似的,只是類型被限定為不同類型而已。這里就不舉例了。注意:沒有float類型。
-
flatMap(..) 接收一個(gè)函數(shù)作為參數(shù),將流中的每個(gè)值都換成另一個(gè)流,然后把所有流連接成一個(gè)流。這個(gè)過程也稱為元素拍平拍扁。
//把每學(xué)生科目分?jǐn)?shù)信息轉(zhuǎn)成一個(gè)新有流 list.stream().flatMap(item->{ //將每個(gè)元素轉(zhuǎn)換成一個(gè)stream String[] info = {item.getStudentName()+"_"+item.getSubject()+"_"+item.getScore()}; Stream<String> newStream = Arrays.stream(info); return newStream; }).forEach(System.out::println); //打印結(jié)果 張三_語文_85 小李_語文_91 張三_數(shù)學(xué)_95 小李_數(shù)學(xué)_95 張三_英語_92
-
flatMapToInt() 跟flatMap的作用一樣 只是類型被限定為IntStream。
//flatMapToInt(..) 和 flatMap的作用一樣 只是類型被限定為IntStream IntStream intStream =list.stream() .flatMapToInt(item ->IntStream.of(item.getScore())); intStream.forEach(System.out::println); //打印結(jié)果 85 91 95 95 92
- faltMapToLong、faltMapToDouble 跟faltMap都類似的,只是類型被限定為不同類型而已。這里就不舉例了。注意:沒有float類型。
排序(sorted...)
-
sorted(), 排序 底層依賴Comparable 實(shí)現(xiàn),也可以提供自定義比較器。
//sorted(), 排序,底層依賴Comparable實(shí)現(xiàn),也可以提供自定義比較器。 //底層依賴Comparable Stream.of(1,1,0,8,2,4,6).sorted().forEach(System.out::println); list.stream() //自定義比較器 按分?jǐn)?shù)由高到低 .sorted((ss1,ss2)->ss1.getScore()<ss2.getScore()?1:ss1.getScore()==ss2.getScore()?0:-1) .forEach(System.out::println); //打印結(jié)果 0 1 1 2 4 6 8 StudentSubject{studentName='張三', subject='數(shù)學(xué)', score=95} StudentSubject{studentName='小李', subject='數(shù)學(xué)', score=95} StudentSubject{studentName='張三', subject='英語', score=92} StudentSubject{studentName='小李', subject='語文', score=91} StudentSubject{studentName='張三', subject='語文', score=85}
消費(fèi)(peek...)
-
peek() 挑選,如同于map,能得到流中的每一個(gè)元素。但map接收的是一個(gè)Function表達(dá)式,有返回值;而peek接收的是Consumer表達(dá)式,沒有返回值??梢岳斫鉃樘崆跋M(fèi)。
//所有同學(xué)所有科目都是100分,哈哈 list.stream().peek(item->item.setScore(100)).forEach(System.out::println); //打印結(jié)果 StudentSubject{studentName='張三', subject='語文', score=100} StudentSubject{studentName='小李', subject='語文', score=100} StudentSubject{studentName='張三', subject='數(shù)學(xué)', score=100} StudentSubject{studentName='小李', subject='數(shù)學(xué)', score=100} StudentSubject{studentName='張三', subject='英語', score=100}
二、終止操作,執(zhí)行中間操作鏈,并產(chǎn)生結(jié)果。當(dāng)這個(gè)操作執(zhí)行后,流就被使用“光”了,無法再被操作。所以這必定是流的最后一個(gè)操作。之后如果想要操作就必須新打開流。終止操作包括:循環(huán)遍歷操作(forEach、forEachOrdered)、收集操作(collect..)、 匹配與聚合操作(allMatch、max、min...)
循環(huán)遍歷操作
-
forEach()、forEachOrdered(),遍歷操作 ,對(duì)每個(gè)數(shù)據(jù)遍歷迭代。
forEach()在上面的例子已使用過,就不舉例了。注意:forEach是一個(gè)終端操作,參數(shù)也是一個(gè)函數(shù),它會(huì)迭代中間操作完成后的每一個(gè)數(shù)據(jù)。
forEachOrdered適用于并行流的情況下進(jìn)行迭代,能確保迭代的有序性。如下面例子:
Stream.of(1,3,7,5,4,9,8,-1) .parallel() .forEachOrdered(item-> System.out.println(Thread.currentThread().getName()+": "+item)); //打印的結(jié)果 ForkJoinPool.commonPool-worker-4: 1 ForkJoinPool.commonPool-worker-2: 3 ForkJoinPool.commonPool-worker-2: 7 ForkJoinPool.commonPool-worker-2: 5 ForkJoinPool.commonPool-worker-2: 4 ForkJoinPool.commonPool-worker-2: 9 ForkJoinPool.commonPool-worker-2: 8 ForkJoinPool.commonPool-worker-2: -1
parallel()提供并行操作的支持。并行這一塊知識(shí),先不在這里深入。
收集操作
-
collect(),收集操作,接收一個(gè)Collector接口的實(shí)現(xiàn),將流轉(zhuǎn)換為其他形式(收集到List,Set,Map等容器中)。
//collect:接收一個(gè)Collector實(shí)例,將流中元素收集成另外一個(gè)數(shù)據(jù)結(jié)構(gòu)。 System.out.println("==========將分?jǐn)?shù) 裝成list========="); //將分?jǐn)?shù) 裝成list List<Integer> scoreList = list.stream().map(StudentSubject::getScore) .collect(Collectors.toList()); scoreList.forEach(System.out::println); System.out.println("=========轉(zhuǎn)成set=========="); //轉(zhuǎn)成set Set<Integer> scoreSet = list.stream().map(StudentSubject::getScore) .collect(Collectors.toSet()); scoreSet.forEach(System.out::println); System.out.println("=========轉(zhuǎn)成map=========="); //轉(zhuǎn)成map,注:key不能相同,否則報(bào)錯(cuò) Map<String,Integer> map = list.stream() .collect(Collectors.toMap(StudentSubject::getSubject, StudentSubject::getScore)); map.forEach((k,v)->System.out.println(k+":"+v)); System.out.println("=========字符串分隔符連接=========="); //字符串分隔符連接 String subjectName = list.stream().map(StudentSubject::getSubject) .collect(Collectors.joining(",", "(", ")")); System.out.println(subjectName); //打印結(jié)果 ==========將分?jǐn)?shù) 裝成list========= 85 95 92 =========轉(zhuǎn)成set========== 85 92 95 =========轉(zhuǎn)成map========== 數(shù)學(xué):95 語文:85 英語:92 =========字符串分隔符連接========== (語文,數(shù)學(xué),英語)
注意下面的兩種寫法的不同之處。
//寫法一 Stream.of("java", "ios", "android", "h5", "rn") .collect(Collectors.toSet()) //set 容器 .forEach(e -> System.out.println(e)); //寫法二 Set<String> setList = Stream.of("java", "ios", "android", "h5", "rn") .collect(Collectors.toSet()) ; setList.forEach(e -> System.out.println(e));
寫法一中collect和forEach同時(shí)使用了終止操作符,大家都會(huì)想到終止操作不是只能用一次就終止了么?其實(shí)forEach不僅僅是Stream中得操作符還是各種集合中得一個(gè)語法糖。
匹配、聚合操作
-
allMatch:匹配操作,接收一個(gè) Predicate 函數(shù),當(dāng)流中每個(gè)元素都符合該斷言時(shí)才返回true,否則返回false
noneMatch:匹配操作,接收一個(gè) Predicate 函數(shù),當(dāng)流中每個(gè)元素都不符合該斷言時(shí)才返回true,否則返回false
anyMatch:匹配操作,接收一個(gè) Predicate 函數(shù),只要流中有一個(gè)元素滿足該斷言則返回true,否則返回false
findFirst:查找操作,返回流中第一個(gè)元素
findAny:查找操作,返回流中的任意元素
count:聚合操作,返回流中元素的總個(gè)數(shù)
max:匹配操作,返回流中元素最大值
min:匹配操作,返回流中元素最小值List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); boolean allMatch = list.stream().allMatch(e -> e > 10); System.out.println("allMatch: "+allMatch); boolean noneMatch = list.stream().noneMatch(e -> e > 10); System.out.println("noneMatch: "+noneMatch); boolean anyMatch = list.stream().anyMatch(e -> e > 5); System.out.println("anyMatch: "+anyMatch); Integer findFirst = list.stream().findFirst().get(); System.out.println("findFirst: "+findFirst); Integer findAny = list.stream().findAny().get(); System.out.println("findAny: "+findAny); long count = list.stream().count(); System.out.println("count: "+count); Integer max = list.stream().max(Integer::compareTo).get(); System.out.println("max: "+max); Integer min = list.stream().min(Integer::compareTo).get(); System.out.println("min: "+min); //打印結(jié)果 allMatch: false noneMatch: true anyMatch: true findFirst: 1 findAny: 1 count: 9 max: 9 min: 1
歸約操作
-
reduce(),歸約操作,把整個(gè)數(shù)據(jù)流的值歸約為一個(gè)值(比如對(duì)所有元素求和,乘啊等)。(如:count、max、min操作的底層就是運(yùn)用了歸約操作)
Optional reduce(BinaryOperator accumulator):
第一次執(zhí)行時(shí),accumulator函數(shù)的第一個(gè)參數(shù)為流中的第一個(gè)元素,第二個(gè)參數(shù)為流中元素的第二個(gè)元素;第二次執(zhí)行時(shí),第一個(gè)參數(shù)為第一次函數(shù)執(zhí)行的結(jié)果,第二個(gè)參數(shù)為流中的第三個(gè)元素;依次類推。
T reduce(T identity, BinaryOperator accumulator):
流程跟上面一樣,只是第一次執(zhí)行時(shí),accumulator函數(shù)的第一個(gè)參數(shù)為identity,而第二個(gè)參數(shù)為流中的第一個(gè)元素。
U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator combiner):
在串行流(stream)中,該方法跟第二個(gè)方法一樣,即第三個(gè)參數(shù)combiner不會(huì)起作用。在并行流(parallelStream)中,我們知道流被fork join出多個(gè)線程進(jìn)行執(zhí)行,此時(shí)每個(gè)線程的執(zhí)行流程就跟第二個(gè)方法reduce(identity,accumulator)一樣,而第三個(gè)參數(shù)combiner函數(shù),則是將每個(gè)線程的執(zhí)行結(jié)果當(dāng)成一個(gè)新的流,然后使用第一個(gè)方法reduce(accumulator)流程進(jìn)行歸約。
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); Integer v = list.stream().reduce((x1, x2) -> x1 + x2).get(); System.out.println("v:"+v); Integer v1 = list.stream().reduce(10, (x1, x2) -> x1 + x2); System.out.println("v1:"+v1); Integer v2 = list.stream().reduce(0, (x1, x2) -> { System.out.println("stream accumulator: x1:" + x1 + " x2:" + x2); return x1 - x2; }, (x1, x2) -> { System.out.println("stream combiner: x1:" + x1 + " x2:" + x2); return x1 * x2; }); System.out.println("v2:"+v2); //并行流 Integer v3 = list.parallelStream().reduce(0, (x1, x2) -> { System.out.println("parallelStream accumulator: x1:" + x1 + " x2:" + x2); return x1 - x2; }, (x1, x2) -> { System.out.println("parallelStream combiner: x1:" + x1 + " x2:" + x2); return x1 * x2; }); System.out.println("v3:"+v3); //打印的結(jié)果 v:120 v1:130 v2:-120 v3:-2004310016
轉(zhuǎn)換操作
-
toArray(),轉(zhuǎn)成數(shù)組,可以提供自定義數(shù)組生成器。
Object aa[] = Stream.of("android","java","IOS").toArray(); StudentSubject s1 = new StudentSubject("張三", "語文", 85); StudentSubject s2 = new StudentSubject("張三", "數(shù)學(xué)", 95); StudentSubject s3 = new StudentSubject("張三", "英語", 92); StudentSubject s4 = new StudentSubject("張三", "物理", 92); List<StudentSubject> list = Arrays.asList(s1,s2,s3,s4); //調(diào)用的stream的toArray的函數(shù) String[] ss = list.stream().toArray(str -> new String[list.size()]); String[] ss1 = list.stream().toArray(String[]::new); Object[] obj1 = list.stream().toArray(); //直接調(diào)用的List接口的toArray函數(shù) String[] ss2 = list.toArray(new String[list.size()]); Object[] obj2 = list.toArray();
前三個(gè),是調(diào)用的stream的toArray的函數(shù),后面的兩個(gè),是直接調(diào)用的List接口的toArray函數(shù)。
總結(jié)
- 了解Stream含義
- 了解Stream的作用
- 了解Stream的操作步驟
- 對(duì)Stream的Api的常用函數(shù)進(jìn)行操作及匯總
注意
- 集合講的是數(shù)據(jù),流講的計(jì)算。
- Stream本身不會(huì)存儲(chǔ)元素。
- Stream不會(huì)改變?cè)磳?duì)象,但它會(huì)返回一個(gè)持有結(jié)果的新Stream。
- Stream操作是延遲執(zhí)行的。這就意味著他們會(huì)等到需要結(jié)果的時(shí)候才執(zhí)行。