強(qiáng)大的 Stream 函數(shù)式編程
前言
Java8(又稱為 Jdk1.8)是 Java 語(yǔ)言開發(fā)的一個(gè)主要版本。Oracle 公司于 2014 年 3 月 18 日發(fā)布 Java8,它支持函數(shù)式編程,新的 JavaScript 引擎,新的日期 API,新的 Stream API 等。Java8 API 添加了一個(gè)新的抽象稱為流 Stream,可以讓你以一種聲明的方式處理數(shù)據(jù)。Stream API 可以極大提高 Java 程序員的生產(chǎn)力,讓程序員寫出高效率、干凈、簡(jiǎn)潔的代碼。
Java8 新特性
Lambda 表達(dá)式 ? Lambda 允許把函數(shù)作為一個(gè)方法的參數(shù)(函數(shù)作為參數(shù)傳遞進(jìn)方法中)。
方法引用 ? 方法引用提供了非常有用的語(yǔ)法,可以直接引用已有 Java 類或?qū)ο螅▽?shí)例)的方法或構(gòu)造器。與 lambda 聯(lián)合使用,方法引用可以使語(yǔ)言的構(gòu)造更緊湊簡(jiǎn)潔,減少冗余代碼。
默認(rèn)方法 ? 默認(rèn)方法就是一個(gè)在接口里面有了一個(gè)實(shí)現(xiàn)的方法。
新工具 ? 新的編譯工具,如:Nashorn 引擎 jjs、類依賴分析器 jdeps。
Stream API ? 新添加的 Stream API(java.util.stream)把真正的函數(shù)式編程風(fēng)格引入到 Java 中。
Date Time API ? 加強(qiáng)對(duì)日期與時(shí)間的處理。
Optional 類 ? Optional 類已經(jīng)成為 Java8 類庫(kù)的一部分,用來(lái)解決空指針異常。
Nashorn JavaScript 引擎 ? Java8 提供了一個(gè)新的 Nashorn javascript 引擎,它允許我們?cè)?JVM 上運(yùn)行特定的 javascript 應(yīng)用。
為什么需要 Steam?
Java8 中的 Stream 是對(duì)集合(Collection)對(duì)象功能的增強(qiáng),它專注于對(duì)集合對(duì)象進(jìn)行各種非常便利、高效的聚合操作,或者大批量數(shù)據(jù)操作。
StreamAPI 借助于同樣新出現(xiàn)的 Lambda 表達(dá)式,極大的提高編程效率和程序可讀性。同時(shí)它提供串行和并行兩種模式進(jìn)行匯聚操作,并發(fā)模式能夠充分利用多核處理器的優(yōu)勢(shì),使用 fork/join 并行方式來(lái)拆分任務(wù)和加速處理過(guò)程。
流的操作種類
中間操作
當(dāng)數(shù)據(jù)源中的數(shù)據(jù)上了流水線后,這個(gè)過(guò)程對(duì)數(shù)據(jù)進(jìn)行的所有操作都稱為“中間操作”。
中間操作仍然會(huì)返回一個(gè)流對(duì)象,因此多個(gè)中間操作可以串連起來(lái)形成一個(gè)流水線。
終端操作
當(dāng)所有的中間操作完成后,若要將數(shù)據(jù)從流水線上拿下來(lái),則需要執(zhí)行終端操作。
終端操作將返回一個(gè)執(zhí)行結(jié)果,這就是你想要的數(shù)據(jù)。
java.util.Stream 使用示例
定義一個(gè)簡(jiǎn)單的學(xué)生實(shí)體類,用于后面的例子演示:
public class Student {
/** 學(xué)號(hào) */
private long id;
/** 姓名 */
private String name;
/** 年齡 */
private int age;
/** 性別 */
private int grade;
/** 專業(yè) */
private String major;
/** 學(xué)校 */
private String school;
// 省略 getter 和 setter
}
// 初始化
List<Student> students = new ArrayList<Student>() {
{
add(new Student(20160001, "孔明", 20, 1, "土木工程", "武漢大學(xué)"));
add(new Student(20160002, "伯約", 21, 2, "信息安全", "武漢大學(xué)"));
add(new Student(20160003, "玄德", 22, 3, "經(jīng)濟(jì)管理", "武漢大學(xué)"));
add(new Student(20160004, "云長(zhǎng)", 21, 2, "信息安全", "武漢大學(xué)"));
add(new Student(20161001, "翼德", 21, 2, "機(jī)械與自動(dòng)化", "華中科技大學(xué)"));
add(new Student(20161002, "元直", 23, 4, "土木工程", "華中科技大學(xué)"));
add(new Student(20161003, "奉孝", 23, 4, "計(jì)算機(jī)科學(xué)", "華中科技大學(xué)"));
add(new Student(20162001, "仲謀", 22, 3, "土木工程", "浙江大學(xué)"));
add(new Student(20162002, "魯肅", 23, 4, "計(jì)算機(jī)科學(xué)", "浙江大學(xué)"));
add(new Student(20163001, "丁奉", 24, 5, "土木工程", "南京大學(xué)"));
}
};
forEach
Stream 提供了新的方法’forEach’來(lái)迭代流中的每個(gè)數(shù)據(jù)。ForEach 接受一個(gè) function 接口類型的變量,用來(lái)執(zhí)行對(duì)每一個(gè)元素的操作。ForEach 是一個(gè)中止操作,它不返回流,所以我們不能再調(diào)用其他的流操作。
以下代碼片段使用 forEach 輸出了 10 個(gè)隨機(jī)數(shù):
// 隨機(jī)生成 10 個(gè) 0,100int 類型隨機(jī)數(shù)
new Random()
.ints(0, 100)
.limit(10)
.forEach(System.out::println);從集合 students 中篩選出所有武漢大學(xué)的學(xué)生:
List<Student> whuStudents = students
.stream()
.filter(student -> "武漢大學(xué)".equals(student.getSchool()))
.collect(Collectors.toList());filter/distinct
filter 方法用于通過(guò)設(shè)置的條件過(guò)濾出元素。Filter 接受一個(gè) predicate 接口類型的變量,并將所有流對(duì)象中的元素進(jìn)行過(guò)濾。該操作是一個(gè)中間操作,因此它允許我們?cè)诜祷亟Y(jié)果的基礎(chǔ)上再進(jìn)行其他的流操作。
以下代碼片段使用 filter 方法過(guò)濾出空字符串:
// 獲取空字符串的數(shù)量
Arrays.asList("abc", "","bc","efg","abcd","", "jkl")
// stream() ? 為集合創(chuàng)建串行流
.stream()
.filter(string -> string.isEmpty())
.count();distinct 方法用于去除重復(fù)元素。
Arrays.asList("a", "c", "ac", "c", "a", "b")
.stream()
.distinct()
.forEach(System.out::println);anyMatch/allMatch/noneMatch
匹配操作有多種不同的類型,都是用來(lái)判斷某一種規(guī)則是否與流對(duì)象相互吻合的。所有的匹配操作都是終結(jié)操作,只返回一個(gè) boolean 類型的結(jié)果。
anyMatch 方法用于判斷集合中是否有任一元素滿足條件。
// 集合中是否有任一元素匹配以'a'開頭
boolean result = Arrays.asList("abc", "","bc","efg","abcd","", "jkl")
.stream()
.anyMatch(s -> s.startsWith("a"));allMatch 方法用于判斷集合中是否所有元素滿足條件。
// 集合中是否所有元素匹配以'a'開頭
boolean result = Arrays.asList("abc", "","bc","efg","abcd","", "jkl")
.stream()
.allMatch(s -> s.startsWith("a"));noneMatch 方法用于判斷集合中是否所有元素不滿足條件。
// 集合中是否沒有元素匹配以'a'開頭
boolean result = Arrays.asList("abc", "","bc","efg","abcd","", "jkl")
.stream()
.noneMatch(s -> s.startsWith("a"));limit/skip
limit 方法用于返回前面 n 個(gè)元素。
Arrays.asList("abc", "","bc","efg","abcd","", "jkl")
.stream()
.filter(string -> !string.isEmpty())
.limit(3)
.forEach(System.out::println);skip 方法用于舍棄前 n 個(gè)元素。
Arrays.asList("abc", "","bc","efg","abcd","", "jkl")
.stream()
.filter(string -> !string.isEmpty())
.skip(1)
.forEach(System.out::println);sorted
sorted 方法用于對(duì)流進(jìn)行排序。Sorted 是一個(gè)中間操作,能夠返回一個(gè)排過(guò)序的流對(duì)象的視圖。流對(duì)象中的元素會(huì)默認(rèn)按照自然順序進(jìn)行排序,除非你自己指定一個(gè) Comparator 接口來(lái)改變排序規(guī)則。
以下代碼片段使用 filter 方法過(guò)濾掉空字符串,并對(duì)其進(jìn)行自然順序排序:
List<String> strings = Arrays.asList("abc", "","bc","efg","abcd","", "jkl");
// 一定要記住, sorted 只是創(chuàng)建一個(gè)流對(duì)象排序的視圖, 而不會(huì)改變?cè)瓉?lái)集合中元素的順序。
strings
.stream()
.filter(string -> !string.isEmpty())
.sorted()
.forEach(System.out::println);
// 輸出原始集合元素, sorted 只是創(chuàng)建排序視圖, 不影響原來(lái)集合順序
strings
.stream()
.forEach(System.out::println);
// 按照字符串長(zhǎng)度進(jìn)行排序, 若兩個(gè)字符串長(zhǎng)度相同, 按照字母順序排列
strings
.stream()
.filter(string -> !string.isEmpty())
// 1. 首先根據(jù)字符串長(zhǎng)度倒序排序; 2. 然后根據(jù)字母順序排列
.sorted(Comparator.comparing(String::length).reversed().thenComparing(String::compareTo))
.forEach(System.out::println);以下代碼片段根據(jù) Person 姓名倒序排序,然后利用 Collectors 返回列表新列表:
List<Person> persons = new ArrayList();
// 1. 生成 5 個(gè) Person 對(duì)象
for (int i = 1; i <= 5; i++) {
Person person = new Person(i, "name" + i);
persons.add(person);
}
// 2. 對(duì) Person 列表進(jìn)行排序, 排序規(guī)則: 根據(jù) Person 姓名倒序排序, 然后利用 Collectors 返回列表新列表;
List<Person> personList = persons
.stream()
.sorted(Comparator.comparing(Person::getName).reversed())
.collect(Collectors.toList());parallel
流操作可以是順序的,也可以是并行的。順序操作通過(guò)單線程執(zhí)行,而并行操作則通過(guò)多線程執(zhí)行??墒褂貌⑿辛鬟M(jìn)行操作來(lái)提高運(yùn)行效率 parallelStream 是流并行處理程序的代替方法。
parallelStream()本質(zhì)上基于 Java7 的 Fork-Join 框架實(shí)現(xiàn),F(xiàn)ork-Join 是一個(gè)處理并行分解的高性能框架,其默認(rèn)的線程數(shù)為宿主機(jī)的內(nèi)核數(shù)。
以下實(shí)例我們使用 parallelStream 來(lái)輸出空字符串的數(shù)量:
// 獲取空字符串的數(shù)量[parallelStream 為 Collection 接口的一個(gè)默認(rèn)方法]
Arrays.asList("abc", "","bc","efg","abcd","", "jkl")
// parallelStream() ? 為集合創(chuàng)建并行流
.parallelStream()
.filter(string -> string.isEmpty())
.count();parallelStream 中 forEachOrdered 與 forEach 區(qū)別:
List<String> strings = Arrays.asList("a", "b", "c");
strings.stream().forEachOrdered(System.out::print); //abc
strings.stream().forEach(System.out::print); //abc
strings.parallelStream().forEachOrdered(System.out::print); //abc
strings.parallelStream().forEach(System.out::print); //bca特別注意:1、千萬(wàn)不要任意地并行 Stream pipeline,如果源頭是來(lái)自 stream.iterate,或者中間使用了中間操作的 limit,那么并行 pipeline 也不可能提升性能。因此,在 Stream 上通過(guò)并行獲取的性能,最好是通過(guò) ArrayList、HashMap、HashSet 和 CouncurrentHashMap 實(shí)例,數(shù)組,int 范圍和 long 范圍等。這些數(shù)據(jù)結(jié)構(gòu)的共性是,都可以被精確、輕松地分成任意大小的子范圍,使并行線程中的分工變得更加輕松。2、Stream pipeline 的終止操作本質(zhì)上也影響了并發(fā)執(zhí)行的效率。并行的最佳操作是做減法,用一個(gè) Stream 的 reduce 方法,將所有從 pipeline 產(chǎn)生的元素都合并在一起,或者預(yù)先打包想 min、max、count 和 sum 這類方法。驟死式操作如 anyMatch、allMatch 和 nonMatch 也都可以并行。由 Stream 的 collect 方法執(zhí)行的操作,都是可變的減法,不是并行的最好選擇,因此并行集合的成本非常高。3、一般來(lái)說(shuō),程序中所有的并行 Stream pipeline 都是在一個(gè)通用的 fork-join 池中運(yùn)行的。只要有一個(gè) pipeline 運(yùn)行異常,都是損害到系統(tǒng)中其它不相關(guān)部分的性能。因此,如果對(duì) Stream 進(jìn)行不恰當(dāng)?shù)牟⑿胁僮鳎赡軐?dǎo)致程序運(yùn)行失敗,或者造成性能災(zāi)難。
map
map 方法用于映射每個(gè)元素到對(duì)應(yīng)的結(jié)果。map 是一個(gè)對(duì)于流對(duì)象的中間操作,通過(guò)給定的方法,它能夠把流對(duì)象中的每一個(gè)元素對(duì)應(yīng)到另外一個(gè)對(duì)象上。
以下代碼片段使用 map 將集合元素轉(zhuǎn)為大寫 (每個(gè)元素映射到大寫)-> 降序排序 ->迭代輸出:
Arrays.asList("abc", "","bc","efg","abcd","", "jkl")
// 通過(guò) stream()方法即可獲取流對(duì)象
.stream()
// 通過(guò) filter()過(guò)濾元素
.filter(string -> !string.isEmpty())
// 通過(guò) map()方法用于映射每個(gè)元素到對(duì)應(yīng)的結(jié)果
.map(String::toUpperCase)
// 通過(guò) sorted()方法用于對(duì)流進(jìn)行排序
.sorted(Comparator.reverseOrder())
// 通過(guò) forEach()方法迭代流中的每個(gè)數(shù)據(jù)
.forEach(System.out::println);篩選出所有專業(yè)為計(jì)算機(jī)科學(xué)的學(xué)生姓名:
List<String> names = students
.stream()
.filter(student -> "計(jì)算機(jī)科學(xué)".equals(student.getMajor()))
.map(Student::getName).collect(Collectors.toList());
計(jì)算所有專業(yè)為計(jì)算機(jī)科學(xué)學(xué)生的年齡之和:
int totalAge = students
.stream()
.filter(student -> "計(jì)算機(jī)科學(xué)".equals(student.getMajor()))
.mapToInt(Student::getAge).sum();
peek
peek 操作接收的是一個(gè) Consumer<T> 函數(shù)。顧名思義 peek 操作會(huì)按照 Consumer<T> 函數(shù)提供的邏輯去消費(fèi)流中的每一個(gè)元素,同時(shí)有可能改變?cè)貎?nèi)部的一些屬性。
按照 Java 團(tuán)隊(duì)的說(shuō)法,peek() 方法存在的主要目的是用調(diào)試,通過(guò) peek() 方法可以看到流中的數(shù)據(jù)經(jīng)過(guò)每個(gè)處理點(diǎn)時(shí)的狀態(tài)。
Stream.of("one", "two", "three","four").filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList());
除去用于調(diào)試,peek() 在需要修改元素內(nèi)部狀態(tài)的場(chǎng)景也非常有用,比如我們想將所有 Student 的名字修改為大寫,當(dāng)然也可以使用 map() 和 flatMap() 實(shí)現(xiàn),但是相比來(lái)說(shuō) peek() 更加方便,因?yàn)槲覀儾⒉幌胩娲髦械臄?shù)據(jù)。
students
.stream()
.peek(student -> student.setName(student.getName().toUpperCase()))
.forEach(System.out::println);
那么 peek() 和 map() 有什么區(qū)別呢?peek 接收一個(gè) Consumer,而 map 接收一個(gè) Function。Consumer 是沒有返回值的,它只是對(duì) Stream 中的元素進(jìn)行某些操作,但是操作之后的數(shù)據(jù)并不返回到 Stream 中,所以 Stream 中的元素還是原來(lái)的元素。而 Function 是有返回值的,這意味著對(duì)于 Stream 的元素的所有操作都會(huì)作為新的結(jié)果返回到 Stream 中。
findFirst/findAny
findAny 能夠從流中隨便選一個(gè)元素出來(lái),它返回一個(gè) Optional 類型的元素。
Optional<String> optional = Arrays.asList("abc", "","bc","efg","abcd","", "jkl")
.stream()
.findAny();
findFirst 能夠從流中選第一個(gè)元素出來(lái),它返回一個(gè) Optional 類型的元素。
Optional<String> optional = Arrays.asList("abc", "","bc","efg","abcd","", "jkl")
.stream()
.findFirst();
collect
collect 方法是一個(gè)終端操作,它接收的參數(shù)是將流中的元素累積到匯總結(jié)果的各種方式(稱為收集器)。
Collectors 工具類提供了許多靜態(tài)工具方法來(lái)為大多數(shù)常用的用戶用例創(chuàng)建收集器,比如將元素裝進(jìn)一個(gè)集合中、將元素分組、根據(jù)不同標(biāo)準(zhǔn)對(duì)元素進(jìn)行匯總等。
Collectors.joining()
Collectors.joining()方法以遭遇元素的順序拼接元素。我們可以傳遞可選的拼接字符串、前綴和后綴。
List<String> strings = Arrays.asList("abc", "","bc","efg","abcd","", "jkl");
// 篩選列表
List<String> filtered = strings
.stream()
.filter(string -> !string.isEmpty())
.collect(Collectors.toList());
// 合并字符串
String mergedString = strings
.stream()
.filter(string -> !string.isEmpty())
.collect(Collectors.joining(","));
Collectors.groupingBy
Collectors.groupingBy 方法根據(jù)項(xiàng)目的一個(gè)屬性的值對(duì)流中的項(xiàng)目作問(wèn)組,并將屬性值作為結(jié)果 Map 的鍵。
List 里面的對(duì)象元素,以某個(gè)屬性來(lái)分組。
// 按學(xué)校對(duì)學(xué)生進(jìn)行分組:
Map<String, List<Student>> groups = students
.stream()
.collect(Collectors.groupingBy(Student::getSchool));
// 多級(jí)分組, 在按學(xué)校分組的基礎(chǔ)之上再按照專業(yè)進(jìn)行分組
Map<String, Map<String, List<Student>>> groups2 = students
.stream()
.collect(
Collectors.groupingBy(Student::getSchool, // 一級(jí)分組,按學(xué)校
Collectors.groupingBy(Student::getMajor))); // 二級(jí)分組,按專業(yè)
統(tǒng)計(jì) List 集合重復(fù)元素出現(xiàn)次數(shù)。
List<String> items = Arrays.asList("apple", "apple", "banana", "apple", "orange", "banana", "papaya");
// 方式一
Map<String, Long> result = items
.stream()
// Function.identity() 返回一個(gè)輸出跟輸入一樣的 Lambda 表達(dá)式對(duì)象, 等價(jià)于形如 t -> t 形式的 Lambda 表達(dá)式.
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
// 方式二
Map<String, Long> result2 = items
.stream()
// Collectors.counting() 計(jì)算流中數(shù)量
.collect(Collectors.groupingBy(String::toString, Collectors.counting()));
// Output :
// {papaya=1, orange=1, banana=2, apple=3}
統(tǒng)計(jì)每個(gè)組的個(gè)數(shù):
Map<String, Long> groups = students
.stream()
.collect(Collectors.groupingBy(Student::getSchool, Collectors.counting()));
累加求和
// 統(tǒng)計(jì)相同姓名, 總年齡大小
Map<String, Integer> sumMap = persons
.stream()
// Collectors.summingInt() 返回流中整數(shù)屬性求和
.collect(Collectors.groupingBy(Person::getName, Collectors.summingInt(Person::getAge)));
轉(zhuǎn)換
// 按照姓名對(duì)學(xué)生分布組,并只保留員工的年齡
Map<String, List<String>> nameMap = persons
.stream()
.collect(Collectors.groupingBy(Person::getName,
Collectors.mapping(Employee::getName, // 下游收集器
Collectors.toList()))); // 更下游的收集器
Collectors.toMap
Collectors.toMap 方法將 List 轉(zhuǎn) Map。
// 根據(jù) Person 年齡生成 Map
Map<Integer, Person> personMap = persons
.stream()
.collect(Collectors.toMap(Person::getAge, person -> person));
// account -> account 是一個(gè)返回本身的 lambda 表達(dá)式, 其實(shí)還可以使用 Function 接口中的一個(gè)默認(rèn)方法代替, 使整個(gè)方法更簡(jiǎn)潔優(yōu)雅.
Map<Integer, Person> personMap = persons
.stream()
.collect(Collectors.toMap(Person::getAge, Function.identity()));
當(dāng) key 重復(fù)時(shí),會(huì)拋出異常:java.lang.IllegalStateException: Duplicate key **
// 針對(duì)重復(fù) key 的, 覆蓋之前的 value
Map<Integer, Person> personMap = persons
.stream()
.collect(Collectors.toMap(Person::getAge, Function.identity(), (person, person2) -> person2));
指定具體收集的 map:
Map<Integer, Person> personMap = persons
.stream()
.collect(Collectors.toMap(Person::getAge, Function.identity(), (person, person2) -> person2, LinkedHashMap::new));
當(dāng) value 為 null 時(shí),會(huì)拋出異常:java.lang.NullPointerException[Collectors.toMap 底層是基于 Map.merge 方法來(lái)實(shí)現(xiàn)的,而 merge 中 value 是不能為 null 的,如果為 null,就會(huì)拋出空指針異常。]
Map<Integer, String> personMap = persons
.stream()
.collect(Collectors.toMap(Person::getAge, Person::getName, (person, person2) -> person2));
// 1. 解決方式 1: 用 for 循環(huán)的方式亦或是 forEach 的方式
Map<Integer, String> personMap = new HashMap<>();
for (Person person : persons) {
personMap.put(person.getAge(), person.getName());
}
// 2. 解決方式 2: 使用 stream 的 collect 的重載方法
Map<Integer, String> personMap = persons
.stream()
.collect(HashMap::new, (m, v) -> m.put(v.getAge(), v.getName()), HashMap::putAll);
Collectors.collectingAndThen
Collectors.collectingAndThen 方法主要用于轉(zhuǎn)換函數(shù)返回的類型。
List 里面的對(duì)象元素,以某個(gè)屬性去除重復(fù)元素。
List<Person> unique = persons
.stream()
.collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparingInt(Person::getAge))), ArrayList::new));
Collectors.partitioningBy
Collectors.partitioningBy 方法主要用于根據(jù)對(duì)流中每個(gè)項(xiàng)目應(yīng)用謂詞的結(jié)果來(lái)對(duì)項(xiàng)目進(jìn)行分區(qū)。
“年齡小于 18”進(jìn)行分組后可以看到,不到 18 歲的未成年人是一組,成年人是另外一組。
Map<Boolean, List<Person>> groupBy = persons
.stream()
.collect(Collectors.partitioningBy(o -> o.getAge() >= 18));
Collectors 收集器靜態(tài)方法:

Collectors 收集器靜態(tài)方法

Collectors 收集器靜態(tài)方法
數(shù)值流的使用
在 Stream 里元素都是對(duì)象,那么,當(dāng)我們操作一個(gè)數(shù)字流的時(shí)候就不得不考慮一個(gè)問(wèn)題,拆箱和裝箱。雖然自動(dòng)拆箱不需要我們處理,但依舊有隱含的成本在里面。Java8 引入了 3 個(gè)原始類型特化流接口來(lái)解決這個(gè)問(wèn)題:IntStream、DoubleStream、LongStream,分別將流中的元素特化為 int、long、double,從而避免了暗含的裝箱成本。
將對(duì)象流映射為數(shù)值流
// 將對(duì)象流映射為數(shù)值流
IntStream intStream = persons
.stream()
.mapToInt(Person::getAge);
默認(rèn)值 OptinalInt
由于數(shù)值流經(jīng)常會(huì)有默認(rèn)值,比如默認(rèn)為 0。數(shù)值特化流的終端操作會(huì)返回一個(gè) OptinalXXX 對(duì)象而不是數(shù)值。
// 每種數(shù)值流都提供了數(shù)值計(jì)算函數(shù), 如 max、min、sum 等
OptionalInt optionalInt = persons
.stream()
.mapToInt(Person::getAge)
.max();
int max = optionalInt.orElse(1);
生成一個(gè)數(shù)值范圍流
// 創(chuàng)建一個(gè)包含兩端的數(shù)值流, 比如 1 到 10, 包含 10:
IntStream intStream = IntStream.rangeClosed(1, 10);
// 創(chuàng)建一個(gè)不包含結(jié)尾的數(shù)值流, 比如 1 到 9:
IntStream range = IntStream.range(1, 9);
將數(shù)值流轉(zhuǎn)回對(duì)象流
// 將數(shù)值流轉(zhuǎn)回對(duì)象流
Stream<Integer> boxed = intStream.boxed();
流的扁平化
案例:對(duì)給定單詞列表 [“Hello”,”World”],你想返回列表[“H”,”e”,”l”,”o”,”W”,”r”,”d”]
方法一:錯(cuò)誤方式
String[] words = new String[]{"Hello", "World"};
List<String[]> a = Arrays.stream(words)
.map(word -> word.split(""))
.distinct()
.collect(Collectors.toList());
a.forEach(System.out::print);
// Output
// [Ljava.lang.String;@12edcd21[Ljava.lang.String;@34c45dca
返回一個(gè)包含兩個(gè) String[]的 list,傳遞給 map 方法的 lambda 為每個(gè)單詞生成了一個(gè) String[]。因此,map 返回的流實(shí)際上是 Stream<String[]>類型的。

方法二:正確方式
String[] words = new String[]{"Hello", "World"};
List<String> a = Arrays.stream(words)
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
a.forEach(System.out::print);
// Output
// HeloWrd使用 flatMap 方法的效果是,各個(gè)數(shù)組并不是分別映射一個(gè)流,而是映射成流的內(nèi)容,所有使用 map(Array::stream)時(shí)生成的單個(gè)流被合并起來(lái),即扁平化為一個(gè)流。

參考博文
[1]. Java 8 中的 Streams API 詳解
[2]. java8 快速實(shí)現(xiàn) List 轉(zhuǎn) map 、分組、過(guò)濾等操作
source:https://morning-pro.github.io/archives/8cef11db.html

喜歡,在看
