因為學習了Lambda表達式,再學習Java8新特性之Stream的API常用函數(shù)使用及說明,兩者結(jié)合起來压彭,代碼就可以玩得更溜,下面做Stream的一些常用函數(shù)使用及說明渗常,方日后溫習壮不,如果發(fā)現(xiàn)有不足之處,請多多指教皱碘。
什么是Stream(流)询一?
Stream是數(shù)據(jù)渠道,用于操作數(shù)據(jù)源(如:集合癌椿,數(shù)組等)轉(zhuǎn)成的元素序列健蕊,對集合中的每個元素進行一系列并行或者串行的流水線操作。
Stream的作用是什么(為什么需要Stream)踢俄?
Stream是Java8的一大亮點缩功,它與java.io包中的InputStream、OutputStream是完全不同的概念都办;也不同于解析XML出來的Stream嫡锌。
Stream是java對集合對象功能的增強势木,對集合對象進行各種非常方便歌懒、高效的聚合操作,或者對大指數(shù)據(jù)操作及皂。Stream的API同樣借助于Lambda表達式(支持函數(shù)式編程,代碼非常簡潔)查剖,提高編程效率和程序的可讀性噪窘,同時它還提供了并行和串行這兩種模式進行匯聚操效扫。默認情況能充分利用cpu的資源(Stream操作是延遲執(zhí)行的直砂,需要結(jié)果的時候才執(zhí)行)。
Stream操作的三步曲
- 創(chuàng)建Stream:通過一個數(shù)據(jù)源(如:數(shù)組静暂,集合)济丘,獲到一個流。
- 中間操作:一個中間操作鏈(如:filter,map等)洽蛀,對數(shù)據(jù)源的數(shù)據(jù)進行處理摹迷。
- 終止操作:一個終止操作,執(zhí)行中間操作鏈郊供,并產(chǎn)生結(jié)果峡碉。
Stream創(chuàng)建的四種方式
-
Collection提供了兩個方法,分別為stream()和parallelStream()驮审。
//通過List集合創(chuàng)建Stream List<String> list = new ArrayList<>(); Stream<String> stream1 = list.stream();//獲取一個順序流 Stream<String> parallelStream = list.parallelStream(); //獲取一個并行流
-
通過Arrays中的靜態(tài)方法stream()獲取一個數(shù)組流鲫寄。
//通過Arrays中的靜態(tài)方法stream()獲取一個數(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)建無限流,這個通常和limit()一起使用类浪,限制流中元素的個數(shù)。 //iterate()接受一個種子值肌似,和一個UnaryOperator(例如 f)费就。然后種子值成為Stream的第一個元素, //f(seed) 為第二個川队,f(f(seed)) 第三個力细,以此類推. Stream<Integer> iterate = Stream.iterate(0, x -> x + 1); iterate.limit(5).forEach(System.out::println); //使用generate()創(chuàng)建無限流,通常跟limit()一起使用固额,限制流中元素的個數(shù)斗躏。 //可以根據(jù)任何計算方式來生成,通過實現(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等)鲸拥,多個中間操作可以連接起來形成一條流水線形式對數(shù)據(jù)源的數(shù)據(jù)進行處理刑赶。注意:如果未觸發(fā)終止操作
(下面會進行介紹)角撞,中間操作是不會執(zhí)行任何處理,在觸發(fā)終止操作時會一次性全部執(zhí)行中間操作鏈并產(chǎn)生結(jié)果劣领。
中間操作有狀態(tài)之分:無狀態(tài)——指元素的處理不受之前元素的影響尖淘;有狀態(tài)——指該操作只有拿到所有元素之后才能繼續(xù)下去村生。
Stream的終止操作(結(jié)束操作)
一個終止操作,執(zhí)行中間操作鏈卫病,并產(chǎn)生結(jié)果蟀苛。終止操作有狀態(tài)之分:非短路——指必須處理所有元素才能得到最終結(jié)果帜平;短路——指遇到某些符合條件的元素就可以得到最終結(jié)果评腺,如 A || B蒿讥,只要A為true芋绸,則無需判斷B的結(jié)果。
Stream的實際操作
我們了解了Java8新特性Stream的API后马昙,開始以代碼實踐操作一遍行楞。
一子房、中間操作就是對容器(集合)的處理過程证杭,包括:篩選(filter、limit乎莉、distinct梦鉴、skip...)肥橙,映射(map存筏、flatMap...)予跌,排序(sorted...)券册,消費(peek..)等 烁焙。先寫一個學生科目分數(shù)對象骄蝇,以便操作九火。
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;
}
}
注意:因為中間操作鏈在沒有執(zhí)行終止操作前是不會執(zhí)行的跨细,所有下面都是以執(zhí)行forEach終止操作來驗證中間操作鏈的使用冀惭。
篩選(filter散休、limit戚丸、distinct夺颤、skip...)
-
filter()世澜,過濾掉某些元素寥裂。如:獲取大90的科目信息
StudentSubject s1 = new StudentSubject("張三", "語文", 85); StudentSubject s2 = new StudentSubject("小李", "語文", 91); StudentSubject s3 = new StudentSubject("張三", "數(shù)學", 95); StudentSubject s4 = new StudentSubject("小李", "數(shù)學", 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ù)學', score=95} StudentSubject{studentName='小李', subject='數(shù)學', score=95} StudentSubject{studentName='張三', subject='英語', score=92}
-
limit()麻养,截斷流鳖昌,使其元素不超過給定的數(shù)量遗遵。
//截流车要,取前面三個元素信息 list.stream().limit(3).forEach(System.out::println); //打印結(jié)果 StudentSubject{studentName='張三', subject='語文', score=85} StudentSubject{studentName='小李', subject='語文', score=91} StudentSubject{studentName='張三', subject='數(shù)學', score=95}
-
distinct()翼岁,通過流所生成的元素的hashCode()和equals()去除重復元素琅坡。
注意:這里有坑,要讓distinct起作用坞淮,就必須在對應實體類中重寫HashCode和equals方法诺擅。
//通過流所生成的元素的hashCode()和equals()去除重復元素烁涌。 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(), 跳過元素抒钱,返回一個扔掉了前N個元素的流。 如果流中元素N個瑞信,則是返回一個空流凡简。與limit(n)互補秤涩。
//扔掉前面三個元素 list.stream().skip(3).forEach(System.out::println); //打印結(jié)果 StudentSubject{studentName='小李', subject='數(shù)學', score=95} StudentSubject{studentName='張三', subject='英語', score=92}
映射(map、flatMap...)
-
map()匀谣,接收一個函數(shù)作為參數(shù),該函數(shù)會被應用到每一個元素上溶锭,并將其他映射成一個新的元素。
//>=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ù)學', score=95} StudentSubject{studentName='小李', subject='數(shù)學', 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終止操作毫痕,下面會詳細說。 //>=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("最大分數(shù):"+intSummaryStatistics.getMax()); //最大值 System.out.println("最小分數(shù):"+intSummaryStatistics.getMin()); //最小值 System.out.println("分數(shù)總和:"+intSummaryStatistics.getSum()); //總計 System.out.println("信息條數(shù):"+intSummaryStatistics.getCount());//個數(shù) System.out.println("平均分數(shù):"+intSummaryStatistics.getAverage());//平均數(shù)返回的是double類型
mapToLong针饥、mapToDouble 跟map都類似的,只是類型被限定為不同類型而已需频。這里就不舉例了丁眼。注意:沒有float類型。
-
flatMap(..) 接收一個函數(shù)作為參數(shù)昭殉,將流中的每個值都換成另一個流苞七,然后把所有流連接成一個流惠啄。這個過程也稱為元素拍平拍扁族奢。
//把每學生科目分數(shù)信息轉(zhuǎn)成一個新有流 list.stream().flatMap(item->{ //將每個元素轉(zhuǎn)換成一個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ù)學_95 小李_數(shù)學_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 實現(xiàn)主巍,也可以提供自定義比較器。
//sorted(), 排序,底層依賴Comparable實現(xiàn)翁垂,也可以提供自定義比較器。 //底層依賴Comparable Stream.of(1,1,0,8,2,4,6).sorted().forEach(System.out::println); list.stream() //自定義比較器 按分數(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ù)學', score=95} StudentSubject{studentName='小李', subject='數(shù)學', score=95} StudentSubject{studentName='張三', subject='英語', score=92} StudentSubject{studentName='小李', subject='語文', score=91} StudentSubject{studentName='張三', subject='語文', score=85}
消費(peek...)
-
peek() 挑選,如同于map,能得到流中的每一個元素车海。但map接收的是一個Function表達式,有返回值致板;而peek接收的是Consumer表達式亚享,沒有返回值情组〉树蓿可以理解為提前消費。
//所有同學所有科目都是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ù)學', score=100} StudentSubject{studentName='小李', subject='數(shù)學', score=100} StudentSubject{studentName='張三', subject='英語', score=100}
二、終止操作人灼,執(zhí)行中間操作鏈投放,并產(chǎn)生結(jié)果蕊肥。當這個操作執(zhí)行后琅锻,流就被使用“光”了茧彤,無法再被操作。所以這必定是流的最后一個操作疆栏。之后如果想要操作就必須新打開流曾掂。終止操作包括:循環(huán)遍歷操作(forEach、forEachOrdered)壁顶、收集操作(collect..)珠洗、 匹配與聚合操作(allMatch、max博助、min...)
循環(huán)遍歷操作
-
forEach()、forEachOrdered()痹愚,遍歷操作 富岳,對每個數(shù)據(jù)遍歷迭代。
forEach()在上面的例子已使用過拯腮,就不舉例了窖式。注意:forEach是一個終端操作,參數(shù)也是一個函數(shù)动壤,它會迭代中間操作完成后的每一個數(shù)據(jù)萝喘。
forEachOrdered適用于并行流的情況下進行迭代,能確保迭代的有序性琼懊。如下面例子:
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()提供并行操作的支持阁簸。并行這一塊知識,先不在這里深入哼丈。
收集操作
-
collect()启妹,收集操作,接收一個Collector接口的實現(xiàn)醉旦,將流轉(zhuǎn)換為其他形式(收集到List饶米,Set桨啃,Map等容器中)。
//collect:接收一個Collector實例檬输,將流中元素收集成另外一個數(shù)據(jù)結(jié)構(gòu)照瘾。 System.out.println("==========將分數(shù) 裝成list========="); //將分數(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不能相同,否則報錯 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é)果 ==========將分數(shù) 裝成list========= 85 95 92 =========轉(zhuǎn)成set========== 85 92 95 =========轉(zhuǎn)成map========== 數(shù)學:95 語文:85 英語:92 =========字符串分隔符連接========== (語文,數(shù)學,英語)
注意下面的兩種寫法的不同之處丧慈。
//寫法一 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同時使用了終止操作符析命,大家都會想到終止操作不是只能用一次就終止了么?其實forEach不僅僅是Stream中得操作符還是各種集合中得一個語法糖伊滋。
匹配碳却、聚合操作
-
allMatch:匹配操作,接收一個 Predicate 函數(shù)笑旺,當流中每個元素都符合該斷言時才返回true昼浦,否則返回false
noneMatch:匹配操作,接收一個 Predicate 函數(shù)筒主,當流中每個元素都不符合該斷言時才返回true关噪,否則返回false
anyMatch:匹配操作,接收一個 Predicate 函數(shù)乌妙,只要流中有一個元素滿足該斷言則返回true使兔,否則返回false
findFirst:查找操作,返回流中第一個元素
findAny:查找操作藤韵,返回流中的任意元素
count:聚合操作虐沥,返回流中元素的總個數(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()欲险,歸約操作,把整個數(shù)據(jù)流的值歸約為一個值(比如對所有元素求和匹涮,乘啊等)天试。(如:count、max然低、min操作的底層就是運用了歸約操作)
Optional reduce(BinaryOperator accumulator):
第一次執(zhí)行時喜每,accumulator函數(shù)的第一個參數(shù)為流中的第一個元素,第二個參數(shù)為流中元素的第二個元素雳攘;第二次執(zhí)行時带兜,第一個參數(shù)為第一次函數(shù)執(zhí)行的結(jié)果,第二個參數(shù)為流中的第三個元素吨灭;依次類推鞋真。
T reduce(T identity, BinaryOperator accumulator):
流程跟上面一樣,只是第一次執(zhí)行時沃于,accumulator函數(shù)的第一個參數(shù)為identity涩咖,而第二個參數(shù)為流中的第一個元素海诲。
U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator combiner):
在串行流(stream)中,該方法跟第二個方法一樣檩互,即第三個參數(shù)combiner不會起作用特幔。在并行流(parallelStream)中,我們知道流被fork join出多個線程進行執(zhí)行,此時每個線程的執(zhí)行流程就跟第二個方法reduce(identity,accumulator)一樣闸昨,而第三個參數(shù)combiner函數(shù)蚯斯,則是將每個線程的執(zhí)行結(jié)果當成一個新的流,然后使用第一個方法reduce(accumulator)流程進行歸約饵较。
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ù)學", 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();
前三個横辆,是調(diào)用的stream的toArray的函數(shù),后面的兩個茄猫,是直接調(diào)用的List接口的toArray函數(shù)狈蚤。
總結(jié)
- 了解Stream含義
- 了解Stream的作用
- 了解Stream的操作步驟
- 對Stream的Api的常用函數(shù)進行操作及匯總
注意
- 集合講的是數(shù)據(jù),流講的計算划纽。
- Stream本身不會存儲元素脆侮。
- Stream不會改變源對象,但它會返回一個持有結(jié)果的新Stream勇劣。
- Stream操作是延遲執(zhí)行的靖避。這就意味著他們會等到需要結(jié)果的時候才執(zhí)行。