詳解Java8函數(shù)式編程之收集器的應(yīng)用
收集器
收集器是一種通用的、從流生成復(fù)雜值的結(jié)構(gòu)??梢允褂盟鼜牧髦猩蒐ist、Set、Map等集合。 收集器都是在流的collect
方法中調(diào)用,并且都在 Collectors
類中。
java 的標(biāo)準(zhǔn)類庫(kù)提供了很多有用的收集器,當(dāng)然了,也可以自己自定義(這個(gè)對(duì)于使用者的要求很高)。
下面提供一個(gè)代碼,用于測(cè)試接下里要說(shuō)的收集器:
提供了一個(gè)簡(jiǎn)單的測(cè)試數(shù)據(jù)
學(xué)號(hào) 姓名 性別 語(yǔ)文 數(shù)學(xué) 英語(yǔ) 物理 政治 總分
09509002 節(jié)強(qiáng) 男 86 90 90 93 90
09509003 楊青 女 90 90 82 91 92
09509006 徐剛 男 78 92 83 90 87
09509111 馬力 男 77 88 99 90 88
09509001 武向麗 女 90 78 83 94 94
09509007 張文靜 女 85 90 79 94 88
09509005 徐小紅 女 78 85 88 93 92
09509009 李姝 女 92 80 75 90 88
09509004 李文華 男 68 59 70 85 90
09509008 夏婧 女 87 65 73 91 95
09509010 王洪 男 66 48 89 70 57
Student 實(shí)體類封裝數(shù)據(jù)
package com.cdragon; public class Student implements Comparable<Student> { private String number; private String name; private String sex; private Integer chinese; private Integer math; private Integer english; private Integer physics; private Integer politics; //省略getter和setter方法,這個(gè)使用IDE自動(dòng)生成特別方便。 //省略toString方法,同上。 @Override public int compareTo(Student s) { return s.getNumber().compareTo(this.getNumber()); } }
LoadData 類加載數(shù)據(jù)到內(nèi)存
package com.cdragon; import java.io.*; import java.util.ArrayList; import java.util.List; public class LoadData { public static List<Student> readFromFile(File file) { List<Student> students = new ArrayList<>(); try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) { String record = null; String header = br.readLine(); //對(duì)于數(shù)據(jù)的第一行頭,暫時(shí)不做處理。 while ((record = br.readLine() ) != null) { Student s = resolveLineToStudent(record); students.add(s); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return students; } private static Student resolveLineToStudent(String record) { String[] array = record.split("\\s+"); // \\s 和 \\s+ 還是有區(qū)別的! Student s = new Student(); s.setNumber(array[0]); s.setName(array[1]); s.setSex(array[2]); s.setChinese(Integer.parseInt(array[3])); s.setMath(Integer.parseInt(array[4])); s.setEnglish(Integer.parseInt(array[5])); s.setPhysics(Integer.parseInt(array[6])); s.setPolitics(Integer.parseInt(array[7])); return s; } }
收集器應(yīng)用
將流轉(zhuǎn)換成其他集合
使用收集器是可以生成其他集合的,例如生成List、Set 和 Map等,下面來(lái)分別舉例:
//生成 List List<Student> studentList = students.stream().collect(Collectors.toList()); //生成 Set Set<Student> studentSet = students.stream().collect(Collectors.toSet()); //生成指定集合 TreeSet<Student> studentTreeSet = students.stream().collect(Collectors.toCollection(TreeSet::new)); //生成 Map Map<String, Student> studentMap = students.stream().collect(Collectors.toMap(Student::getNumber, s->s)); studentMap.forEach((no, s)->{ System.out.println(no + "->" + s); });
說(shuō)明:通常的 toList() 和 toSet() 方法是不指定生成集合的具體類型,這是由系統(tǒng)來(lái)選擇最合適的類型,但是有時(shí)候我們必須返回特定的類型集合,這就用到了 toCollection() 方法,這個(gè)方法可以指定需要生成的集合的類型,這是使用方法引用進(jìn)行簡(jiǎn)化代碼:TreeSet::new
測(cè)試結(jié)果:
注意:生成 Map 的方式較為復(fù)雜,因?yàn)樾枰瑫r(shí)指定鍵和值。
轉(zhuǎn)換成值
使用收集器生成一個(gè)值。
最大值和最小值
Collectors 類中的 maxBy 和 minBy 允許用戶按照某種特定順序生成一個(gè)值。它們的作用就如同它們的名字一樣,分別是尋找最大值和最小值。
我寫成一個(gè)方法,這樣調(diào)用比較方便。
/** * 獲取單科最高分。 * */ public static Optional<Student> minOrMaxSubject(List<Student> students, Comparator<? super Student> comparator) { return students.stream().collect(Collectors.maxBy(comparator)); }
說(shuō)明:使用 maxBy 或者 minBy 必須傳入一個(gè) Comparator 對(duì)象作為參數(shù),即參數(shù)為一個(gè)比較器。
測(cè)試代碼
//這個(gè)文件的路徑應(yīng)該使用自己指定的 List<Student> students = LoadData.readFromFile(new File("src/grade.txt")); //獲取數(shù)學(xué)最高分學(xué)生 Optional<Student> s1 = TestStream.minOrMaxSubject(students, Comparator.comparing(Student::getMath)); //獲取英語(yǔ)最高分 Optional<Student> s2 = TestStream.minOrMaxSubject(students, Comparator.comparing(Student::getEnglish)); System.out.println(s1.get()); System.out.println(s2.get());
測(cè)試結(jié)果
說(shuō)明:如果想要測(cè)試最低分,只要把上面的 maxBy 改成 minBy 就行了,或者直接更進(jìn)一步,修改參數(shù)為collect里面?zhèn)魅氲暮瘮?shù),不過(guò)那樣就會(huì)顯得格外復(fù)雜,而且不止可以查最高分和最低分了。
平均值
上面看過(guò)了最大值和最小值,現(xiàn)在來(lái)看看平均值。 下面這個(gè)方法是用來(lái)求單科平均分的。
/** * 獲取單科平均分 * */ public static double averageScore(List<Student> students, ToIntFunction<? super Student> mapper) { return students.stream().collect(Collectors.averagingInt(mapper)); }
測(cè)試代碼
List<Student> students = LoadData.readFromFile(new File("src/grade.txt")); double math = TestStream.averageScore(students, Student::getMath); System.out.println("數(shù)學(xué)的單科平均分:" + math);
測(cè)試結(jié)果
數(shù)據(jù)分塊
數(shù)據(jù)分塊是指收集器將流分為兩個(gè)集合,注意分塊是只能分成兩塊。這里標(biāo)準(zhǔn)類庫(kù)提供了一個(gè)收集器 partitioningBy,它接受一個(gè)流,并將其分為兩個(gè)部分。返回的結(jié)果為一個(gè) Map,鍵只有兩種:true 或者 false,值是滿足對(duì)應(yīng)條件的集合。
例如我想知道某們成績(jī) 90分以上和一下的學(xué)生分別是哪些。
/** * 以特定分?jǐn)?shù)劃分不同學(xué)生,例如90分以上(含90分)和90分一下。 * 結(jié)果是一個(gè)Map集合,只有兩個(gè)元素,true false 個(gè)對(duì)應(yīng)一個(gè)集合。 * */ public static Map<Boolean, List<Student>> splitScore(List<Student> students, Predicate<? super Student> predicate) { return students.stream().collect(Collectors.partitioningBy(predicate)); }
說(shuō)明:partitioningBy的參數(shù)為一個(gè) Predicate 對(duì)象,這個(gè)和過(guò)濾器的很相似,功能上可以對(duì)比學(xué)習(xí)。
測(cè)試代碼
//數(shù)學(xué)成績(jī)以90分來(lái)劃分學(xué)生 Map<Boolean, List<Student>> booleanListMap = TestStream.splitScore(students, stu->stu.getMath()>=90); booleanListMap.forEach((bool, list)->{ System.out.println("數(shù)學(xué)成績(jī)大于90分:" + bool); list.forEach(System.out::println); System.out.println("========================"); });
測(cè)試結(jié)果
數(shù)據(jù)分組
數(shù)據(jù)分組是一種更為自然的分割數(shù)據(jù)操作,與將數(shù)據(jù)分成true和false兩部分不同,可以使用任意值對(duì)數(shù)據(jù)分組。比如使用性別對(duì)學(xué)生進(jìn)行分組。這很像SQL中的 groupBy 操作。
/** * 數(shù)據(jù)分組 * 這里以性別來(lái)分組 * */ public static Map<String, List<Student>> groupBy(List<Student> students) { return students.stream().collect(Collectors.groupingBy(Student::getSex)); }
測(cè)試代碼
Map<String, List<Student>> stringListMap = TestStream.groupBy(students); stringListMap.forEach((sex, list)->{ System.out.println("性別:" + sex); list.forEach(System.out::println); System.out.println("============"); });
測(cè)試結(jié)果
字符串
收集流中的數(shù)據(jù)最后生成一個(gè)字符串,這是一個(gè)很平常的操作。 例如一個(gè)所有學(xué)生的姓名列表,使用傳統(tǒng)的迭代列表操作代碼如下:
/** * 獲取所有學(xué)生姓名的字符串 * 傳統(tǒng)的迭代操作 * 格式如下:[張三,李四] * */ public static String nameStr1(List<Student> students) { StringBuilder builder = new StringBuilder("["); for (Student stu : students) { if (builder.length() > 1){ builder.append(","); } String name = stu.getName(); builder.append(name); } builder.append("]"); return builder.toString(); }
然后是使用收集器進(jìn)行操作,代碼如下: 這里我添加一些細(xì)節(jié)處理,學(xué)生的排名按照學(xué)生的總成績(jī)從高到底排列,這是很符合習(xí)慣的。
/** * 獲取所有學(xué)生姓名的字符串 * 函數(shù)式方法 * 格式如下:[張三,李四] * * 注意,他只能連接字符串,所有這里使用 map 操作,將 Student 轉(zhuǎn)成 String(學(xué)生姓名) * */ public static String nameStr2(List<Student> students) { return students.stream() .sorted(Comparator.comparing(s -> { return s.getChinese() + s.getMath() + s.getEnglish() + s.getPhysics() + s.getPolitics(); }, Comparator.reverseOrder())) //(sum1, sum2)-> sum2.compareTo(sum1) .map(Student::getName) .collect(Collectors.joining(",","[","]")); }
說(shuō)明:這里的 sorted 需要傳入一個(gè) Comparator 對(duì)象,但是可以使用靜態(tài)方法 Compring 進(jìn)行簡(jiǎn)化,但是它只是指定需要排序的標(biāo)準(zhǔn),并沒(méi)有說(shuō)是從小到大還是從大到小,后來(lái)才發(fā)現(xiàn),這個(gè)是默認(rèn)的:大小到大排序。但是我需要的是使用從大到小,然后發(fā)現(xiàn)原來(lái) compring 還有重載方法,具有兩個(gè)參數(shù),另一個(gè)參數(shù)是可以指定大小順序的,所以第二個(gè)參數(shù)我傳入了一個(gè) Lambda 表達(dá)式:
(sum1, sum2)-> sum2.compareTo(sum1)
但是如果這樣使用的話,還不如直接使用 Lambda 表達(dá)式創(chuàng)建 Comparator 對(duì)象方便呢,后來(lái)發(fā)現(xiàn)這個(gè) IDE 比較智能,它指出這句代碼,可以被替換為:
Comparator.reverseOrder();// 看意思就知道是 反序的意思。
這樣看來(lái)使用 Comparator 靜態(tài)的 comparing 方法還是比直接創(chuàng)建 Comparator 對(duì)象簡(jiǎn)單一些。
注意:如果不需要排序的話,就只有一個(gè)map方法和join方法了。這個(gè)map方法的作用是映射(我一開始把它和map集合總是搞混了),將Student對(duì)象映射為name字符串,然后使用 join 方法進(jìn)行連接。
組合收集器
收集器還可以組合起來(lái)使用,這個(gè)和 SQL 感覺(jué)更像了,幾乎具有函數(shù)式編程的語(yǔ)言,都有SQL那種處理數(shù)據(jù)的方式,例如最大值、最小值和分組等操作。 考慮對(duì)于學(xué)生按照性別分組,然后再分別統(tǒng)計(jì)男女生的人數(shù)。(這個(gè)在 SQL 里面也是一個(gè)基本的練習(xí)。)
/** * 組合收集器 * 這里以性別來(lái)分組,再分別計(jì)數(shù) * */ public static Map<String, Long> combination(List<Student> students) { return students.stream().collect(Collectors.groupingBy(Student::getSex, Collectors.counting())); }
測(cè)試代碼
Map<String, Long> stringLongMap = TestStream.combination(students); stringLongMap.forEach((sex, count)->{ System.out.println("性別:" + sex + ", 人數(shù):" + count); });
測(cè)試結(jié)果
使用流的其他操作
對(duì)于流的使用,應(yīng)該達(dá)到一個(gè)較為熟練的地步,但是由于沒(méi)有什么機(jī)會(huì)實(shí)踐,還是比較陌生。下面介紹幾個(gè)我寫的方法,來(lái)看看流的操作:
//通過(guò)過(guò)濾器選擇特定的學(xué)生,過(guò)濾器用于過(guò)濾,然后選擇第一個(gè)學(xué)生。 //這里應(yīng)該加一個(gè)排序操作比較好。 public static Optional<Student> selectStudent(List<Student> students, Predicate<? super Student> pre) { return students.stream().filter(pre).findFirst(); } public static List<Student> orderBy(List<Student> students, Comparator<? super Student> comparator) { if (comparator != null){ return students.stream().sorted(comparator).collect(Collectors.toList()); } else { return students.stream().sorted().collect(Collectors.toList()); } } //獲取一列數(shù)據(jù)。不是一行學(xué)生記錄,是一列。 public static List<?> getAColumn(List<Student> students, Function<? super Student, ?> mapper) { return students.stream().map(mapper).collect(Collectors.toList()); } /** * 獲取所有學(xué)生的總分和學(xué)號(hào) * */ public static Map<String, Integer> getSum(List<Student> students) { return students.stream().collect(Collectors.toMap(Student::getNumber, stu->{ return stu.getChinese() + stu.getEnglish() + stu.getMath() + stu.getPhysics() + stu.getPolitics(); })); } /** * peek 和 forEach 的區(qū)別 * peek 是一個(gè)中間操作,forEach 是一個(gè)終結(jié)操作。 * * 假如實(shí)現(xiàn)一個(gè)功能:每個(gè)學(xué)生的某門科目分?jǐn)?shù)進(jìn)行修改。 * * peek 操作后得到的仍然是一個(gè) stream,此時(shí)可以進(jìn)一步操作, * 但是 forEach 是終結(jié)操作,操作結(jié)束,流就結(jié)束了,如果需要進(jìn)一步處理, * 必須再次進(jìn)行得到流的操作。 * */ public static List<Student> addScore1(List<Student> students, Consumer<? super Student> action) { return students.stream().peek(action).collect(Collectors.toList()); } public static void addScore2(List<Student> students, Consumer<? super Student> action) { students.stream().forEach(action); } //指定返回類型為 LinkedList,這時(shí)一個(gè)測(cè)試,并不是說(shuō)需要這樣寫。 //多數(shù)情況下,我們還是應(yīng)該使用 ArrayList public static List<Student> addScore3(List<Student> students, Consumer<? super Student> action) { return students.stream().peek(action).collect(Collectors.toCollection(LinkedList::new)); }
對(duì)于其中的幾個(gè)進(jìn)行測(cè)試(不是全部方法,如果感興趣,可以自己嘗試。):
//對(duì)于學(xué)生進(jìn)行排序,參數(shù)為一個(gè)比較器,參數(shù)為空的話,使用默認(rèn)的 sorted 排序。 //測(cè)試代碼 按照學(xué)號(hào)排序(默認(rèn)從小到大) TestStream.orderBy(students,Comparator.comparing(Student::getNumber)).forEach(System.out::println); //按照學(xué)號(hào)排序(從大到?。? TestStream.orderBy(students,Comparator.comparing(Student::getNumber, Comparator.reverseOrder())).forEach(System.out::println); //使用默認(rèn)的排序 TestStream.orderBy(students).forEach(System.out::println); //獲取一列學(xué)生的記錄,例如這里是英語(yǔ)成績(jī),這里返回值我使用通配符應(yīng)該沒(méi)有錯(cuò)吧 //因?yàn)榉祷財(cái)?shù)據(jù)可能為 String 也可能是 Integer TestStream.getAColumn(students,Student::getEnglish).forEach(System.out::println); //測(cè)試學(xué)生的總分 TestStream.getSum(students).forEach((no, stu)->{ System.out.println(no + " -> " + stu); });
總結(jié)
雖然這個(gè)收集器(也可以說(shuō)Java的函數(shù)式編程)有的使用起來(lái)感覺(jué)很簡(jiǎn)單、簡(jiǎn)潔(當(dāng)然了它的目的也是如此),但是內(nèi)部實(shí)現(xiàn)看著還是感覺(jué)無(wú)從下手,大量使用了泛型、通配符上下限這些方面的知識(shí),這對(duì)于使用來(lái)說(shuō)可以忽視,但是如果想要深入了解的話,還是很有難度的,我就先看到這里吧,消化消化知識(shí)再說(shuō),哈哈!
到此這篇關(guān)于詳解Java8函數(shù)式編程之收集器的應(yīng)用的文章就介紹到這了,更多相關(guān)Java函數(shù)式編程收集器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot2.3.0配置JPA的實(shí)現(xiàn)示例
這篇文章主要介紹了SpringBoot2.3.0配置JPA的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08springboot項(xiàng)目數(shù)據(jù)庫(kù)密碼如何加密
在我們?nèi)粘i_發(fā)中,我們可能很隨意把數(shù)據(jù)庫(kù)密碼直接明文暴露在配置文件中,今天就來(lái)聊聊在springboot項(xiàng)目中如何對(duì)數(shù)據(jù)庫(kù)密碼進(jìn)行加密,感興趣的可以了解一下2021-07-07關(guān)于ArrayList初始化容量的問(wèn)題
這篇文章主要介紹了關(guān)于ArrayList初始化容量的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03SpringCloud中的斷路器(Hystrix)和斷路器監(jiān)控(Dashboard)
本篇主要介紹的是SpringCloud中的斷路器(Hystrix)和斷路器指標(biāo)看板(Dashboard)的相關(guān)使用知識(shí),需要的朋友可以參考下2019-06-06Spring中@Import的各種用法以及ImportAware接口詳解
這篇文章主要介紹了Spring中@Import的各種用法以及ImportAware接口詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10