Java?中如何使用?stream?流
前言
如果你了解過 Liunx ,了解過 Liunx 的中管道命令 | ,那么你會(huì)發(fā)現(xiàn),其實(shí) Java 8 的 stream 和 Liunx 非常類似的。
Liunx 中的管道命令也是將上一個(gè)命令的輸出流作為下一條命令的輸入流。
今天主要聊起的是如何使用 stream 流,關(guān)于它為什么被引入,有什么樣的優(yōu)勢(shì),還有一些平時(shí)未曾注意到的知識(shí)點(diǎn)的話,就在下一次再講吧~
能基礎(chǔ)的使用,是深入了解它的一個(gè)基礎(chǔ)吧,我覺得~
在本文中,你將會(huì)看到Stream API支持的許多操作。這些操作能讓你快速完成復(fù)雜的數(shù)據(jù)查詢,如篩選、切片、映射、查找、匹配和歸約。
一、篩選和切片
1.1、篩選 filter
filter 會(huì)接受一個(gè) Predicate 接口的參數(shù),其本質(zhì)就是一個(gè)布爾值函數(shù)(官方稱為謂詞,說成白話,即為一個(gè)布爾值函數(shù))
準(zhǔn)備好的數(shù)據(jù)~
? ? ? static ? List<Student> students = new ArrayList<>(); ?? ? ? ?static { ? ? ? ? ? ? ? ? students.add(new Student("學(xué)生A", "大學(xué)1", 18)); ? ? ? ? ?students.add(new Student("學(xué)生A", "大學(xué)1", 18)); ? ? ? ? ?students.add(new Student("學(xué)生A", "大學(xué)1", 18)); ? ? ? ? ?students.add(new Student("學(xué)生A", "大學(xué)1", 18)); ? ? ? ? ?students.add(new Student("學(xué)生B", "大學(xué)1", 18)); ? ? ? ? ?students.add(new Student("學(xué)生C", "大學(xué)1", 19)); ? ? ? ? ?students.add(new Student("學(xué)生D", "大學(xué)2", 20)); ? ? ? ? ?students.add(new Student("學(xué)生E", "大學(xué)2", 21)); ? ? ? ? ?students.add(new Student("學(xué)生F", "大學(xué)2", 20)); ? ? ? ? ?students.add(new Student("學(xué)生G", "大學(xué)3", 22)); ? ? ? ? ?students.add(new Student("學(xué)生H", "大學(xué)3", 23)); ? ? ? ? ?students.add(new Student("學(xué)生I", "大學(xué)3", 19)); ? ? ? ? ?students.add(new Student("學(xué)生J", "大學(xué)4", 20)); ? ? }
1、從中篩選出小于20的學(xué)生們組成一個(gè)新的集合
jdk 8 之前的寫法:
?List<Student> result = new ArrayList<>(); ?for (Student student : students) { ? ? ?if (student.getAge() < 20) { ? ? ? ? ?result.add(student); ? ? } ?}
Jdk 8 及之后的寫法:使用stream流操作
?/** ? ? ? * 選出小于20的學(xué)生組成一個(gè)集合 ? ? ? * ? ? ? * @param students ? ? ? */ ?private static List<Student> selectAgeLt18(List<Student> students) { ? ? ?// 最基礎(chǔ)的寫法, filter的參數(shù)是一個(gè) Predicate,而它是一個(gè)FunctionalInterface 式的接口, 唯一的接口就是表示一個(gè)參數(shù)的謂詞(布爾值函數(shù))。 ? ? ?// ? ? ? List<Student> list = students.stream().filter(new Predicate<Student>() { ? ? ?// ? ? ? ? ? @Override ? ? ?// ? ? ? ? ? public boolean test(Student student) { ? ? ?// ? ? ? ? ? ? ? return student.getAge()<20; ? ? ?// ? ? ? ? ? } ? ? ?// ? ? ? }).collect(Collectors.toList()); ? ? ?// 因此可以簡化寫成 以下這種寫法 ? ? ?// ? ? ? List<Student> list = students.stream().filter(student -> { ? ? ?// ? ? ? ? ? return student.getAge() < 20; ? ? ?// ? ? ? }).collect(Collectors.toList()); ? ? ?//又因?yàn)閒ilter 的參數(shù)實(shí)際上是一個(gè)lambda表達(dá)式,當(dāng)只有一條返回語句時(shí),又可以省略大括號(hào)和return ? ? ?List<Student> list = students.stream().filter(student -> student.getAge() < 20).collect(Collectors.toList()); ? ? ?return list; ?}
1.2、去重 distinct
distinct()它會(huì)返回一個(gè)元素各異(根據(jù)流所生成元素的 hashCode和equals方法實(shí)現(xiàn))的流。
jdk 8之前對(duì)集合的一些去重方式
?/** ? ? ? * 去重操作,去除掉數(shù)據(jù)集合中重復(fù)的數(shù)據(jù) ? ? ? */ ?private static void selectSchoolRepresent(List<Student> students) { ? ? ?// ? ? ? ? jdk 8之前的一些方式, ? ? ?// ? ? ? ? 1、set集合去重 ? ? ?HashSet<Student> set = new HashSet<>(); ? ? ?for (Student student : students) { ? ? ? ? ?set.add(student); ? ? } ? ? ?// ? ? ? ? 還可以簡寫成 ? ? ?List<Student> newList = new ArrayList<>(new HashSet<>(students)); ?? ? ? ?// ? ? ? ? 2、 利用 list的contains() 方法 ? ? ?List<Student> list = new ArrayList<>(); ? ? ?for (Student student : students) { ? ? ? ? ?if(!list.contains(student)){ ? ? ? ? ? ? ?list.add(student); ? ? ? ? } ? ? } ?}
Java 8 及之后使用stream中的 distinct()方法,其實(shí)咋說勒,就是方便,其他的也木有
?/** ? ? ? * 去重操作,去除掉數(shù)據(jù)集合中重復(fù)的數(shù)據(jù) ? ? ? */ ?private static void selectSchoolRepresent(List<Student> students) { ? ? ?List<Student> collect = students.stream().distinct().collect(Collectors.toList()); ? ? ?collect.forEach(System.out::println); ?}
1.3、切片 limit
流支持limit(n)方法,該方法會(huì)返回一個(gè)不超過給定長度的流。
如果流是有序的,則最多會(huì)返回前n個(gè)元素。無序的則不會(huì)以任何方式排序。
Jdk 8 之前的寫法
?/** ? ? ? * 選出集合中前五位同學(xué) 組成一個(gè)新的集合 ? ? ? * ? ? ? * @param students ? ? ? */ ?private static void selectLimit(List<Student> students) { ? ? ?List<Student> list = new ArrayList<>(); ? ? ?for (int i = 0; i < students.size(); i++) { ? ? ? ? ?if (i < 5) { ? ? ? ? ? ? ?list.add(students.get(i)); ? ? ? ? } ? ? } ? ? ?list.forEach(System.out::println); ?}
Jdk 8的 stream 流中的 limit 的寫法
?/** ? ? ? * 選出集合中前五位同學(xué) 組成一個(gè)新的集合 ? ? ? * ? ? ? * @param students ? ? ? */ ?private static void selectLimit(List<Student> students) { ?? ? ? ?List<Student> collect = students.stream().limit(5).collect(Collectors.toList()); ?? ? ? ?collect.forEach(System.out::println); ?}
1.4、跳過元素 skip
流還支持skip(n)方法,返回一個(gè)扔掉了前n個(gè)元素的流。如果流中元素不足n個(gè),則返回一 個(gè)空流。
?/** ? ? ? * 從第二個(gè)同學(xué)開始組成新的集合 ? ? ? * ? ? ? * @param students ? ? ? */ ?private static void selectSkip(List<Student> students) { ? ? ?List<Student> collect = students.stream().skip(2).collect(Collectors.toList()); ? ? ?collect.forEach(System.out::println); ? ? ?/** ? ? ? ? ? * Student(name=學(xué)生A, school=大學(xué)1, age=18, score=90.0) ? ? ? ? ? * Student(name=學(xué)生A, school=大學(xué)1, age=18, score=76.0) ? ? ? ? ? * Student(name=學(xué)生B, school=大學(xué)1, age=18, score=91.0) ? ? ? ? ? * Student(name=學(xué)生C, school=大學(xué)1, age=19, score=65.0) ? ? ? ? ? * Student(name=學(xué)生D, school=大學(xué)2, age=20, score=80.0) ? ? ? ? ? * Student(name=學(xué)生E, school=大學(xué)2, age=21, score=78.0) ? ? ? ? ? * Student(name=學(xué)生F, school=大學(xué)2, age=20, score=67.0) ? ? ? ? ? * Student(name=學(xué)生G, school=大學(xué)3, age=22, score=87.0) ? ? ? ? ? * Student(name=學(xué)生H, school=大學(xué)3, age=23, score=79.0) ? ? ? ? ? * Student(name=學(xué)生I, school=大學(xué)3, age=19, score=92.0) ? ? ? ? ? * Student(name=學(xué)生J, school=大學(xué)4, age=20, score=84.0) ? ? ? ? ? */ ?}
1.5、排序 sorted
這個(gè)就是排序啦,沒啥能說的啦吧~偷個(gè)懶哈
? ? ?/** ? ? ? * 給這群學(xué)生按年齡排序 ? ? ? * ? ? ? * ? ? ? * @param students ? ? ? */ ? ? ?private static void sortedDemo(List<Student> students) { ? ? ? ? ?List<Student> collect = students.stream() ? ? ? ? ? ? ? ? .sorted((student1, student2) -> student1.getAge() - student2.getAge()) ? ? ? ? ? ? ? ? .collect(Collectors.toList()); ? ? ? ? ?collect.forEach(System.out::println); ?? ? ? ? ? ?/** ? ? ? ? ? * Student(name=學(xué)生A, school=大學(xué)1, age=18, score=98.0) ? ? ? ? ? * Student(name=學(xué)生A, school=大學(xué)1, age=18, score=91.0) ? ? ? ? ? * Student(name=學(xué)生A, school=大學(xué)1, age=18, score=90.0) ? ? ? ? ? * Student(name=學(xué)生A, school=大學(xué)1, age=18, score=76.0) ? ? ? ? ? * Student(name=學(xué)生B, school=大學(xué)1, age=18, score=91.0) ? ? ? ? ? * Student(name=學(xué)生C, school=大學(xué)1, age=19, score=65.0) ? ? ? ? ? * Student(name=學(xué)生I, school=大學(xué)3, age=19, score=92.0) ? ? ? ? ? * Student(name=學(xué)生D, school=大學(xué)2, age=20, score=80.0) ? ? ? ? ? * Student(name=學(xué)生F, school=大學(xué)2, age=20, score=67.0) ? ? ? ? ? * Student(name=學(xué)生J, school=大學(xué)4, age=20, score=84.0) ? ? ? ? ? * Student(name=學(xué)生E, school=大學(xué)2, age=21, score=78.0) ? ? ? ? ? * Student(name=學(xué)生G, school=大學(xué)3, age=22, score=87.0) ? ? ? ? ? * Student(name=學(xué)生H, school=大學(xué)3, age=23, score=79.0) ? ? ? ? ? */ ? ? }
1.6、小結(jié)與綜合應(yīng)用
filter 、distinct、limit、skip、sorted 對(duì)比起 Java 8 之前的一些實(shí)現(xiàn),從我個(gè)人看來是方便了許多的。
如果是看起來不習(xí)慣,我覺得可以試著多用上幾次,會(huì)慢慢愛上它的。
綜合應(yīng)用
filter 、distinct、limit、skip、sorted 這些操作,他們的執(zhí)行結(jié)果的返回值仍然是 stream,所以在使用中,他們完全可以無縫鏈接.
如: 我要去這一群學(xué)生中找到 年齡在 20 歲以下,分?jǐn)?shù)在90分以上的前3名學(xué)生。
? ? ?/** ? ? ? * 如: 我要去這一群學(xué)生中找到 年齡在 20 歲以下,分?jǐn)?shù)在90分以上的前3名學(xué)生。 ? ? ? * ? ? ? * @param students ? ? ? */ ? ? ?private static void select(List<Student> students) { ? ? ? ? ?List<Student> collect = students.stream() ? ? ? ? ? ? ? ? .filter(student -> student.getAge() < 20) ? ? ? ? ? ? ? ? .filter(student -> student.getScore() > 90.0) ? ? ? ? ? ? ? ? .limit(3) ? ? ? ? ? ? ? ? .collect(Collectors.toList()); ? ? ? ? ?collect.forEach(System.out::println); ? ? ? ? ?/** ? ? ? ? ? * Student(name=學(xué)生A, school=大學(xué)1, age=18, score=98.0) ? ? ? ? ? * Student(name=學(xué)生A, school=大學(xué)1, age=18, score=91.0) ? ? ? ? ? * Student(name=學(xué)生B, school=大學(xué)1, age=18, score=91.0) ? ? ? ? ? */ ? ? }
二、映射 map
這個(gè)map的映射其實(shí)不光Java 有,JavaScript 也是有的,用法我感覺是一樣的~
一個(gè)非常常見的數(shù)據(jù)處理套路就是從某些對(duì)象中選擇信息。比如在SQL里,你可以從表中選 擇一列。
用我個(gè)人的話來說,filter 是用來過濾元素的,而這一小節(jié)的 map 是用來創(chuàng)建一個(gè)新的元素。(在官方中的使用的映射一詞,是因?yàn)閙ap 會(huì)接受一個(gè)函數(shù)作為參數(shù),并且將其映射成一個(gè)新的元素。)
可能說起來還是不如實(shí)踐來的實(shí)在。
數(shù)據(jù)還是上一節(jié)造的那些數(shù)據(jù)。
如:找出集合中所有學(xué)生的姓名,去除掉重復(fù)的名稱,組成一個(gè) List 集合
?/** ? ? ? * 找出集合中所有學(xué)生的姓名,去除掉重復(fù)的名稱,組成一個(gè) List<String> 集合 ? ? ? * ? ? ? * @param students ? ? ? */ ?private static void selectAllStudentName(List<Student> students) { ?? ? ? ?List<String> collect = students.stream().map(new Function<Student, String>() { ? ? ? ? ?@Override ? ? ? ? ?public String apply(Student student) { ? ? ? ? ? ? ?return student.getName(); ? ? ? ? } ? ? }).distinct().collect(Collectors.toList()); ?? ? ? ?List<String> list = students.stream().map(student -> { ? ? ? ? ?return student.getName(); ? ? }).distinct().collect(Collectors.toList()); ?? ? ? ?List<String> collect1 = students.stream() ? ? ? ? .map(student -> student.getName()) ? ? ? ? .distinct() ? ? ? ? .collect(Collectors.toList()); ? ? ?collect1.forEach(System.out::println); ?? ? ? ?/** ? ? ? ? ? * 學(xué)生A ? ? ? ? ? * 學(xué)生B ? ? ? ? ? * 學(xué)生C ? ? ? ? ? * 學(xué)生D ? ? ? ? ? * 學(xué)生E ? ? ? ? ? * 學(xué)生F ? ? ? ? ? * 學(xué)生G ? ? ? ? ? * 學(xué)生H ? ? ? ? ? * 學(xué)生I ? ? ? ? ? * 學(xué)生J ? ? ? ? ? */ ?}
三、查找和匹配
3.1、匹配 anyMatch、allMatch和noneMatch 方法
anyMatch方法可以回答“流中是否有一個(gè)元素能匹配給定的謂詞”
這里的謂詞也就是filter那部分所說的一個(gè) 布爾值函數(shù)。
其實(shí)看到 any 的第一眼,大家也明白,任一,只有集合中含有你需要的,那就是返回 true。
? ? ?/** ? ? ? * 判斷這群學(xué)生中有木有年齡大于20歲的學(xué)生 ? ? ? * ? ? ? * @param students ? ? ? */ ? ? ?private static void anyMatchDemo(List<Student> students) { ? ? ? ? ?boolean anyMatch = students.stream().anyMatch(student -> student.getAge() > 20); ? ? ? ? ?System.out.println(anyMatch); ? ? ? ? ?/** ? ? ? ? ? * true ? ? ? ? ? */ ? ? }
還有 allMatch 和 noneMatch 他們都和 anyMatch 類似。
allMatch 要求全部元素都滿足要求,
noneMatch 則是要求全部元素都不滿足要求時(shí)返回true。
3.2、查找 findAny 與 findFirst
findAny 方法將返回當(dāng)前流中的任意元素。
它的搭檔一般是 filter,和 filter 使用可以實(shí)現(xiàn)很多操作。
如我想要當(dāng)確定這群學(xué)生中有20歲以上的學(xué)生時(shí)立馬返回結(jié)果。
?/** ? ? ? * 當(dāng)確定這群學(xué)生中有20歲以上的學(xué)生時(shí)即返回。 ? ? ? * ? ? ? * @param students ? ? ? */ ?private static void findAnyDemo(List<Student> students) { ? ? ?Optional<Student> student1 = students.stream().filter(student -> student.getAge() > 20).findAny(); ? ? ?Student student = student1.get(); ? ? ?System.out.println(student); ? ? ?/** ? ? ? ? ? * Student(name=學(xué)生E, school=大學(xué)2, age=21, score=78.0) ? ? ? ? ? */ ?}
這里的 Optional 是 Java 8 新增的一個(gè) 容器類,作用就是用來判斷存在和不存在。也就是大家常談到的更優(yōu)雅的判空操作。
Optional 幾個(gè)常見的Api
isPresent()
將在Optional包含值的時(shí)候返回true, 否則返回falseifPresent(Consumer<T> block)
會(huì)在值存在的時(shí)候執(zhí)行給定的代碼塊。T get()
會(huì)在值存在時(shí)返回值,否則拋出一個(gè)NoSuchElement異常。T orElse(T other)
會(huì)在值存在時(shí)返回值,否則返回一個(gè)默認(rèn)值。
詳細(xì)的用法,大家也可以去了解了解,這也是非常好用的一個(gè)東東。
findFirst 其實(shí)就是確定返回第一個(gè)元素。它也和 filter 一起搭配使用。
咋一看, findany 和 findFirst 不是一樣嗎,其實(shí)在你對(duì)于返回的第一個(gè)元素沒有明確要求時(shí),你可以理解成他們確實(shí)就是一樣的。
但其實(shí)他們真實(shí)區(qū)別并非體現(xiàn)如此,而是在 stream 中的并行流中。
今天沒談這個(gè),大家可以去了解了解,了解并行流就會(huì)和常常聊到的性能相關(guān)啦,到底那種好一些啥的~
3.3、小結(jié)
anyMatch、allMatch和noneMatch這三個(gè)操作都用到了我們所謂的短路。
就是我們剛學(xué)語法時(shí)的 && 和 || 運(yùn)算符,這也算是他們?cè)?stream 的實(shí)現(xiàn)。
最簡單的理解方式,就是他們通過遍歷,組成了一個(gè)很長很長的布爾表達(dá)式。
除去他們能實(shí)現(xiàn)短路操作, findAny 與 findFirst 也是同樣如此,并非都需要遍歷結(jié)束才會(huì)得到最終的結(jié)果。只要在其中某一次中達(dá)成條件,即可返回結(jié)果。
四、歸約
官方的說法,成為歸約,如果用簡單的話語來說的話,可以理解為將多個(gè)東西歸為一堆。
4.1、元素求和 reduce
? ? ?private static void reduceDemo() { ? ? ? ? ?List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); ?? ? ? ? ? ?Integer reduce = list.stream().reduce(0, (a, b) -> a + b); ? ? ? ? ?System.out.println("list集合的總和:==>" + reduce); ?? ? ? ? ? ?Integer reduce1 = list.stream().reduce(1, (a, b) -> a * b); ? ? ? ? ?System.out.println("list集合中的元素相乘結(jié)果==>" + reduce1); ?? ? ? ? ? ?Optional<Integer> reduce2 = list.stream().reduce((a, b) -> a + b); ? ? ? ? ?Integer integer = reduce2.get(); ? ? ? ? ?System.out.println("list 集合的總和==>"+integer); ? ? ? ? ?/** ? ? ? ? ? * list集合的總和:==>55 ? ? ? ? ? * list集合中的元素相乘結(jié)果==>3628800 ? ? ? ? ? * list 集合的總和==>55 ? ? ? ? ? */ ? ? }
reduce接受兩個(gè)參數(shù):
一個(gè)初始值,這里是0;
一個(gè) BinaryOperator 來將兩個(gè)元素結(jié)合起來產(chǎn)生一個(gè)新值,BinaryOperator 也是funcational 接口,所以也可以使用lambda 表達(dá)式 lambda (a, b) -> a + b 來表示。
?Integer reduce = list.stream().reduce(0, (a, b) -> a + b);
另外還有一個(gè)重載函數(shù),就是沒有初始值版本的,它的返回值是Optional<Integer>
的容器類。
? ?Optional<Integer> reduce2 = list.stream().reduce((a, b) -> a + b);
最大值與最小值:
?private static void reduceDemo2(){ ? ? ?List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); ? ? ?Optional<Integer> max = list.stream().reduce(Integer::max); ? ? ?Optional<Integer> min = list.stream().reduce(Integer::min); ? ? ?System.out.println("max==>"+max.get()); ? ? ?System.out.println("min==>"+min.get()); ? ? ?/** ? ? ? ? ? * max==>10 ? ? ? ? ? * min==>1 ? ? ? ? ? */ ?}
后記
到此這篇關(guān)于Java 中如何使用 stream 流的文章就介紹到這了,更多相關(guān)Java stream 流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMVC中的ResourceUrlProviderExposingInterceptor詳解
這篇文章主要介紹了SpringMVC中的ResourceUrlProviderExposingInterceptor詳解,ResourceUrlProviderExposingInterceptor是Spring MVC的一個(gè)HandlerInterceptor,用于向請(qǐng)求添加一個(gè)屬性,需要的朋友可以參考下2023-12-12SpringBoot整合RestTemplate用法的實(shí)現(xiàn)
本篇主要介紹了RestTemplate中的GET,POST,PUT,DELETE、文件上傳和文件下載6大常用的功能,具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08Java 利用binarySearch實(shí)現(xiàn)抽獎(jiǎng)計(jì)算邏輯
這篇文章主要介紹了Java 利用binarySearch實(shí)現(xiàn)抽獎(jiǎng)計(jì)算邏輯,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-12-12SpringBoot利用ThreadPoolTaskExecutor批量插入百萬級(jí)數(shù)據(jù)
在處理大量數(shù)據(jù)時(shí),為了提高效率和性能,通常需要采用批量插入的方式,本文主要介紹了SpringBoot利用ThreadPoolTaskExecutor批量插入百萬級(jí)數(shù)據(jù),具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03Springboot?jpa使用sum()函數(shù)返回結(jié)果如何被接收
這篇文章主要介紹了Springboot?jpa使用sum()函數(shù)返回結(jié)果如何接收,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02SpringBoot自動(dòng)配置原理,你真的懂嗎?(簡單易懂)
這篇文章主要介紹了SpringBoot自動(dòng)配置原理,你真的懂嗎?本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-05-05java實(shí)現(xiàn)簡單銀行管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡單銀行管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12