一文帶你深入了解Java8 Stream流式編程
我在項(xiàng)目當(dāng)中,很早就開始使用Java 8的流特性進(jìn)行開發(fā)了,但是一直都沒有針對(duì)這塊進(jìn)行開發(fā)總結(jié)。這次就對(duì)這一塊代碼知識(shí)做一次全面總結(jié),在總結(jié)的過程中去發(fā)現(xiàn)自己的不足,同時(shí)方便日后開發(fā)查詢。
在實(shí)際項(xiàng)目當(dāng)中,若能熟練使用Java8 的Stream流特性進(jìn)行開發(fā),就比較容易寫出簡潔優(yōu)雅的代碼。目前市面上很多開源框架,如Mybatis- Plus、kafka Streams以及Flink流處理等,都有一個(gè)相似的地方,即用到Stream流特性,其寫出的代碼簡潔而易懂,當(dāng)然,若是在不熟悉流特性的基礎(chǔ)上而貿(mào)然去使用Stream開發(fā)的話,難免會(huì)寫出一手bug。
此文主要適合新手。
一、Stream中間操作
Stream的中間操作是指在流鏈當(dāng)中,可以對(duì)數(shù)據(jù)進(jìn)行處理操作,包括filter過濾、map映射轉(zhuǎn)換、flatMap合并、distinct去重、sorted排序等操作。這些操作都會(huì)返回一個(gè)新的Stream流對(duì)象,可以通過鏈?zhǔn)秸{(diào)用多個(gè)中間操作進(jìn)行復(fù)雜的數(shù)據(jù)處理。需要注意的是,中間操作需要具有終止操作才會(huì)觸發(fā)。
下面按類別講解Stream常見的中間操作。
1.1、filter:過濾出符合條件的元素
filter()方法常用于實(shí)現(xiàn)數(shù)據(jù)過濾,即可以對(duì)集合、數(shù)組等數(shù)據(jù)源篩選出符合指定條件的元素,并返回一個(gè)新的流。
假設(shè)有一個(gè)黑名單手機(jī)號(hào)列表,需要篩選出其中所有開頭為“133”的元素,那么可以通過filter()實(shí)現(xiàn)——
//將數(shù)組轉(zhuǎn)換為一個(gè)字符串列表 List<String> numbers = Arrays.asList("13378520000","13278520000","13178520000","13358520000"); //通過stream()方法創(chuàng)建一個(gè)流,接著使用filter()方法過濾出前綴為“133”的元素,最終通過collect() 方法將結(jié)果收集到一個(gè)新列表中 List<String> filterdNumbers = numbers.stream().filter(s -> s.startsWith("133")).collect(Collectors.toList()); System.out.println(filterdNumbers); //打印結(jié)果:[13378520000, 13358520000]
1.2、map:映射轉(zhuǎn)換元素
map()方法用于對(duì)流中的每個(gè)元素進(jìn)行映射操作,將其轉(zhuǎn)換為另一個(gè)元素或者提取其中的信息,并返回一個(gè)新的流。
根據(jù)以下兩個(gè)案例分別學(xué)習(xí)map()將元素轉(zhuǎn)換為另一個(gè)元素以及提取元素其中的信息——
1.2.1、轉(zhuǎn)換元素
假設(shè)有一個(gè)手機(jī)號(hào)字符列表,需要根據(jù)前7位來確定手機(jī)號(hào)歸屬地,那么就需要獲取所有手機(jī)號(hào)前7位子字符串,可以使用map()方法實(shí)現(xiàn):
List<String> numbers = Arrays.asList("13378520000","13278520000","13178520000","13558520000"); //通過stream()方法創(chuàng)建一個(gè)流,使用map()方法將每個(gè)字符串轉(zhuǎn)換為截取前7位的字符,最后使用collect()方法將結(jié)果收集到一個(gè)新列表中 List<String> filterdNumbers = numbers.stream().map(s -> s.substring(0,7)).collect(Collectors.toList()); System.out.println(filterdNumbers); //打印結(jié)果:[1337852, 1327852, 1317852, 1355852]
1.2.2、提取元素信息
假設(shè)有一個(gè)用戶對(duì)象列表,我們需要提取其中每個(gè)對(duì)象的手機(jī)號(hào),可以使用map()方法實(shí)現(xiàn):
List<People> peopleList = Arrays.asList( new People("王二","13378520000"), new People("李二","13278520000"), new People("張四","13178520000") ); //通過stream()方法創(chuàng)建一個(gè)流,使用map()方法提取每個(gè)用戶的手機(jī)號(hào),最后使用collect()方法將結(jié)果收集到一個(gè)新列表中 List<String> tel = peopleList.stream().map(People::getTel).collect(Collectors.toList()); System.out.println(tel); //打印結(jié)果:[13378520000, 13278520000, 13178520000]
1.3、flatMap:將多個(gè)流合并為一個(gè)流
flatMap()方法可以實(shí)現(xiàn)多對(duì)多的映射,或者將多個(gè)列表合并成一個(gè)列表操作。
1.3.1、實(shí)現(xiàn)多對(duì)多的映射
假設(shè)有兩組余額列表A和B,需要將A組每個(gè)元素都與B組所有元素依次進(jìn)行相加,可以使用flatMap實(shí)現(xiàn)該多對(duì)多的映射——
List<Integer> listA = Arrays.asList(1, 2, 3); List<Integer> listB = Arrays.asList(4, 5, 6); List<Integer> list = listA.stream().flatMap(a -> listB.stream().map(b -> a +b)).collect(Collectors.toList()); System.out.println(list); //打印結(jié)果: [5, 6, 7, 6, 7, 8, 7, 8, 9]
1.3.2、將多個(gè)列表合并成一個(gè)列表
假設(shè)有一個(gè)包含多個(gè)手機(jī)號(hào)字符串列表的列表,現(xiàn)在需要合并所有手機(jī)號(hào)字符串成為一個(gè)列表,可以使用flatMap()方法實(shí)現(xiàn):
List<List<String>> listOfLists = Arrays.asList( Arrays.asList("13378520000", "13278520000"), Arrays.asList("13178520000", "13558520000"), Arrays.asList("15138510000", "15228310000") ); List<String> flatMapList = listOfLists.stream().flatMap(Collection::stream).collect(Collectors.toList()); System.out.println(flatMapList); //打印結(jié)果:[13378520000, 13278520000, 13178520000, 13558520000, 15138510000, 15228310000]
1.4、distinct:去除重復(fù)的元素
distinct()方法可以用來去除流中的重復(fù)元素,生成無重復(fù)的列表。
假設(shè)有一個(gè)包含重復(fù)手機(jī)號(hào)字符串的列表,可以使用distinct()去重操作——
List<String> numbers = Arrays.asList("13378520000", "15138510000","13178520000", "15138510000"); List<String> disNumbers = numbers.stream().distinct().collect(Collectors.toList()); System.out.println(disNumbers); //打印結(jié)果:[13378520000, 15138510000, 13178520000]
注意一點(diǎn)的是,distinct用于針對(duì)流作去重操作時(shí),需要確定流中元素實(shí)現(xiàn)了equals()和hashCode()方法,因?yàn)檫@兩個(gè)方法是判斷兩個(gè)對(duì)象是否相等的標(biāo)準(zhǔn)。
1.5、sorted:排序元素
sorted()方法用于對(duì)流中的元素進(jìn)行排序。
假設(shè)需要對(duì)一組People對(duì)象按照年齡排序,下面分別按照升序排序和降序排序——
1.5.1、升序排序
默認(rèn)情況下,是升序排序——
List<People> peopleList = Arrays.asList( new People("王二",20), new People("李二",30), new People("張四",31) ); List<People> newpeopleList=peopleList.stream().sorted(Comparator.comparing(People::getAge)).collect(Collectors.toList()); //打印結(jié)果 newpeopleList.stream().forEach(System.out::println);
打印結(jié)果:
People{name='王二', age=20}
People{name='李二', age=30}
People{name='張四', age=31}
1.5.2、降序排序
通過reversed()方法進(jìn)行逆序排序,也就是將升序排序進(jìn)行倒序排序——
List<People> peopleList = Arrays.asList( new People("王二",20), new People("李二",30), new People("張四",31) ); List<People> newpeopleList = peopleList.stream().sorted(Comparator.comparing(People::getAge).reversed()).collect(Collectors.toList()); //打印結(jié)果 newpeopleList.stream().forEach(System.out::println);
?打印結(jié)果:
People{name='張四', age=31}
People{name='李二', age=30}
People{name='王二', age=20}
1.6、peek:查看每個(gè)元素的信息,但不修改流中元素的狀態(tài)
peek()方法用于查看流中的元素而不會(huì)修改流中元素的狀態(tài),可以在流中的任何階段使用,不會(huì)影響到流的操作,也不會(huì)終止流的操作。
List<String> telList = Arrays.asList("13378520000","13278520000","13178520000","13558520000"); telList.stream().peek(t -> System.out.println(t)) .map(t -> t.substring(0,3)) .peek(t -> System.out.println(t)) .collect(Collectors.toList());
打印結(jié)果:
13378520000
133
13278520000
132
peek()方法和forEach很類似,都是可以用于遍歷流中的元素,但是,兩者之間存在較大的區(qū)別。主要一點(diǎn)是,forEach在流中是一個(gè)終止操作,一旦調(diào)用它,就意味著Stream流已經(jīng)被處理完成,不能再進(jìn)行任何操作,例如,無法在forEach之后針對(duì)流進(jìn)行map、filter等操作,但peek方法可以,以上的案例可以看出,在第一次調(diào)用peek打印一個(gè)元素后,該元素還可以接著進(jìn)行map操作,進(jìn)行字符串的前三位截取。
這是peek()方法和forEach最大的區(qū)別。
1.7、limit 和 skip:截取流中的部分元素
limit()和skip()都是用于截取Stream流中部分元素的方法,兩者區(qū)別在于,limit()返回一個(gè)包含前n個(gè)元素的新流,skip()則返回一個(gè)丟棄前n個(gè)元素后剩余元素組成的新流。
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; System.out.print("取數(shù)組前5個(gè)元素:"); Arrays.stream(arr).limit(5).forEach(n -> System.out.print(n + " ")); // 輸出結(jié)果為:1 2 3 4 5 System.out.print("跳過前3個(gè)元素,取剩余數(shù)組元素:"); Arrays.stream(arr).skip(3).forEach(n -> System.out.print(n + " ")); // 輸出結(jié)果為:4 5 6 7 8 9 10
二、Stream終止操作
Stream的終止操作是指執(zhí)行Stream流鏈中最后一個(gè)步驟,到這一步就會(huì)結(jié)束整個(gè)流處理。在Java8中,Stream終止操作包括forEach、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst和findAny等。這些終止操作都有返回值。需要注意一點(diǎn)是,如果沒有執(zhí)行終止操作的話,Stream流是不會(huì)觸發(fā)執(zhí)行的,例如,一個(gè)沒有終止操作的peek()方法代碼是不會(huì)執(zhí)行進(jìn)而打印——
list.stream().peek(t -> System.out.println("ddd"))
當(dāng)加上終止操作話,例如加上collect,就會(huì)打印出“ddd”——
list.stream().peek(t -> System.out.println("ddd")).collect(Collectors.toList());
下面按類別分別講解各個(gè)終止操作的使用。
2.1、forEach:遍歷流中的每個(gè)元素
該forEach前面已經(jīng)提到,這里不做過多介紹。
2.2、count:統(tǒng)計(jì)流中元素的數(shù)量
count可以統(tǒng)計(jì)流中元素的數(shù)量并返回結(jié)果。
假設(shè)有一個(gè)包含多個(gè)手機(jī)號(hào)字符串的列表,需要統(tǒng)計(jì)去重后的手機(jī)號(hào)數(shù)量,就可以使用count方法——
List<String> numbers = Arrays.asList("13378520000", "15138510000","13178520000", "15138510000"); long count = numbers.stream() .distinct()//去重 .count();//統(tǒng)計(jì)去重后的手機(jī)號(hào) System.out.println(count); //打印結(jié)果:3
2.3、reduce:將流中的所有元素歸約成一個(gè)結(jié)果
reduce()可以將流中的所有元素根據(jù)指定規(guī)則歸約成一個(gè)結(jié)果,并將該結(jié)果返回。
常用語法格式如下:
Optional<T> result = stream.reduce(BinaryOperator<T> accumulator);
可見,reduce方法會(huì)返回一個(gè)Optional類型的值,表示歸約后的結(jié)果,需要通過get()方法獲取Optional里的值。
假設(shè)有一個(gè)包含多個(gè)手機(jī)號(hào)字符串的List列表,需要在去重之后,再將列表所有字符串拼按照逗號(hào)間隔接成一個(gè)字符串返回,那么就可以通過reduce來實(shí)現(xiàn)——
List<String> numbers = Arrays.asList("13378520000", "15138510000","13178520000", "15138510000"); Optional result = numbers.stream() .distinct() //去重 .reduce((a ,b) -> a+","+b);//指定規(guī)則為,相臨兩個(gè)字符通過逗號(hào)“,”間隔 System.out.println(result.get()); //打印結(jié)果:13378520000,15138510000,13178520000
2.4、collect:將流中的元素收集到一個(gè)容器中,并返回該容器
collect的作用是將流中的元素收集到一個(gè)新的容器中,返回該容器。打個(gè)比喻,它就像一個(gè)采摘水果的工人,負(fù)責(zé)將水果一個(gè)個(gè)采摘下來,然后放進(jìn)一個(gè)籃子里,最后將籃子交給你。我在前面的案例當(dāng)中,基本都有用到collect,例如前面2.1的filter過濾用法中的List filterdNumbers = numbers.stream().filter(s -> s.startsWith("133")).collect(Collectors.toList()),就是將過濾出前綴為“133”的字符串,將這些過濾處理后的元素交給collect這個(gè)終止操作。這時(shí)collect就像采摘水果的員工,把采摘為前綴“133”的“水果”通過toList()方法收集到一個(gè)新的List容器當(dāng)中,然后交給你。最后你就可以得到一個(gè)只裝著前綴為“133”的元素集合。
在Java8的collect方法中,除里toList()之外,還提供了例如toSet,toMap等方法滿足不同的場(chǎng)景,根據(jù)名字就可以知道,toSet()返回的是一個(gè)Set集合,toMap()返回的是一個(gè)Map集合。
2.5、min 和 max:找出流中的最小值和最大值
min和max用來查找流中的最小值和最大值。
假設(shè)需要在查找出用戶列表中年齡最小的用戶,可以按照以下代碼實(shí)現(xiàn)——
List<People> peopleList = Arrays.asList( new People("王二",20), new People("李二",30), new People("張四",31) ); //查找年齡最小的用戶,若沒有則返回一個(gè)null People people = peopleList.stream().min(Comparator.comparing(People::getAge)).orElse(null); System.out.println(people); //打印結(jié)果:People{name='王二', age=20}
max的用法類似,這里不做額外說明。
2.6、anyMatch、allMatch 和 noneMatch:判斷流中是否存在滿足指定條件的元素
2.6.1、anyMatch
anyMatch用于判斷,如果流中至少有一個(gè)元素滿足給定條件,那么返回true,反之返回false,即 true||false為true這類的判斷。
假設(shè)在一個(gè)手機(jī)號(hào)字符串的List列表當(dāng)中,判斷是否包含前綴為“153”的手機(jī)號(hào),就可以使用anyMatch——
List<String> numbers = Arrays.asList("13378520000", "15138510000","13178520000", "15338510000"); boolean hasNum = numbers.stream().anyMatch(n -> n.startsWith("153")); System.out.println(hasNum); //打印結(jié)果:true
2.6.2、allMatch
allMatch用于判斷,流中的所有元素是否都滿足給定條件,滿足返回true,反之false,即true&&false為false這類判斷。
假設(shè)在一個(gè)手機(jī)號(hào)字符串的List列表當(dāng)中,判斷手機(jī)號(hào)是否都滿足前綴為“153”的手機(jī)號(hào),就可以用allMatch——
List<String> numbers = Arrays.asList("13378520000", "15138510000","13178520000", "15338510000"); boolean hasNum = numbers.stream().allMatch(n -> n.startsWith("153")); System.out.println(hasNum); //打印結(jié)果:false
2.6.3、noneMatch
noneMatch用于判斷,如果流中沒有任何元素滿足給定的條件,返回true,如果流中有任意一個(gè)條件滿足給定條件,返回false,類似!true為false的判斷。
假設(shè)在一個(gè)手機(jī)號(hào)字符串的List列表當(dāng)中,判斷手機(jī)號(hào)是否都不滿足前綴為“153”的手機(jī)號(hào),就可以用noneMatch——
List<String> numbers = Arrays.asList("13378520000", "15138510000","13178520000", "1238510000"); //numbers里沒有前綴為“153”的手機(jī)號(hào) boolean hasNum = numbers.stream().noneMatch(n -> n.startsWith("153")); System.out.println(hasNum); //打印結(jié)果:true
這三個(gè)方法其實(shí)存在一定互相替代性,例如在3.6.1中,滿足!anyMatch表示所有手機(jī)號(hào)都不為“153”前綴,才得到true,這不就是noneMatch,主要看在項(xiàng)目當(dāng)中如何靈活應(yīng)用。
2.7、findFirst 和 findAny:返回流中第一個(gè)或任意一個(gè)元素
2.7.1、findFirst
findFirst用于返回流中第一個(gè)元素,如果流為空話,則返回一個(gè)空的Optional對(duì)象——
假設(shè)需要對(duì)一批同手機(jī)號(hào)的黑名單用戶按照時(shí)間戳降序排序,然后取出第一個(gè)即時(shí)間戳為最早的用戶,就可以使用findFirst——
List<People> peopleList = Arrays.asList( new People("王二","13178520000","20210409"), new People("李二","13178520000","20230401"), new People("張四","13178520000","20220509"), new People("趙六","13178520000","20220109") ); /** * 先按照時(shí)間升序排序,排序后的結(jié)果如下: * People{name='王二', tel='13178520000', time='20210409'} * People{name='趙六', tel='13178520000', time='20220109'} * People{name='張四', tel='13178520000', time='20220509'} * People{name='李二', tel='13178520000', time='20230401'} * *排序后,People{name='王二', tel='13178520000', time='20210409'}成了流中的第一個(gè)元素 */ People people = peopleList.stream().sorted(Comparator.comparing(People::getTime)).findFirst().orElse(null); System.out.println(people); //打印結(jié)果:People{name='王二', tel='13178520000', time='20210409'}
2.7.2、findAny
findAny返回流中的任意一個(gè)元素,如果流為空,則通過Optional對(duì)象返回一個(gè)null。
假設(shè)有一個(gè)已經(jīng)存在的黑名單手機(jī)號(hào)列表blackList,現(xiàn)在有一批新的手機(jī)號(hào)列表phoneNumber,需要基于blackList列表過濾出phoneNumber存在的黑名單手機(jī)號(hào),最后從過濾出來的黑名單手機(jī)號(hào)當(dāng)中挑選出來出來任意一個(gè),即可以通過findAny實(shí)現(xiàn)——
//blackList是已經(jīng)存在的黑名單列表 List<String> blackList = Arrays.asList("13378520000", "15138510000"); //新來的手機(jī)號(hào)列表 List<String> phoneNumber = Arrays.asList("13378520000", "13178520000", "1238510000","15138510000","13299920000"); String blackPhone = phoneNumber.stream() //過濾出phoneNumber有包含在blackList的手機(jī)號(hào),這類手機(jī)號(hào)即為黑名單手機(jī)號(hào)。 .filter(phone -> blackList.contains(phone)) //獲取過濾確定為黑名單手機(jī)號(hào)的任意一個(gè) .findAny() //如果沒有則返回一個(gè)null .orElse(null); System.out.println(blackPhone); //打印結(jié)果:13378520000
三、并行流
前面的案例主要都是以順序流來講解,接下來,就是講解Stream的并行流。在大數(shù)據(jù)量處理場(chǎng)景下,使用并行流可以提高某些操作效率,但同樣存在一些需要考慮的問題,并非所有情況下都可以使用。
3.1、什么是并行流:并行流的概念和原理
? 并行流是指通過將數(shù)據(jù)按照一定的方式劃分成多個(gè)片段分別在多個(gè)處理器上并行執(zhí)行,這就意味著,可能處理完成的數(shù)據(jù)順序與原先排序好的數(shù)據(jù)情況是不一致的。主要是用在比較大的數(shù)據(jù)量處理情況,若數(shù)據(jù)量太少,效率并不比順序流要高,因?yàn)榈讓悠鋵?shí)就使用到了多線程的技術(shù)。
并行流的流程原理如下:
1、輸入數(shù)據(jù):并行流的初始數(shù)據(jù)一般是集合或者數(shù)組,例如Arrays.asList("13378520000", "13178520000", "1238510000","15138510000","13299920000");
2、劃分?jǐn)?shù)據(jù):將初始數(shù)據(jù)平均分成若干個(gè)子集,每個(gè)子集可以在不同的線程中獨(dú)立進(jìn)行處理,這個(gè)過程通常叫“分支”(Forking),默認(rèn)情況下,Java8并行流使用到了ForkJoinPool框架,會(huì)將Arrays.asList("13378520000", "13178520000", "1238510000","15138510000","13299920000")劃分成更小的顆粒進(jìn)行處理,可能會(huì)將該數(shù)組劃分成以下三個(gè)子集:
[13378520000, 13178520000] [1238510000, 13338510000] [13299920000]
3、處理數(shù)據(jù):針對(duì)劃分好的子集并行進(jìn)行相同的操作,例如包括過濾(filter)、映射(map)、去重(distinct)等,這個(gè)過程通常叫“計(jì)算”(Computing),例如需要過濾為前綴包括“133”的字符集合,那么,各個(gè)子集,就會(huì)處理得到以下結(jié)果:
[13378520000] [13338510000] []
4、合并結(jié)果:將所有子集處理完成的結(jié)果進(jìn)行匯總,得到最終結(jié)果。這個(gè)過程通常叫“合并”(Merging),結(jié)果就會(huì)合并如下:
[13378520000,13338510000]
5、返回結(jié)果:返回最終結(jié)果。
通俗而言,就是順序流中,只有一個(gè)工人在摘水果,并行流中,是多個(gè)工人同時(shí)在摘水果。
3.2、創(chuàng)建并行流:通過 parallel() 方法將串行流轉(zhuǎn)換為并行流
可以通過parallel()方法將順序流轉(zhuǎn)換為并行流,操作很簡單,只需要在順序流上調(diào)用parallel()即可。
List<String> numbers = Arrays.asList("13378360000","13278240000","13178590000","13558120000"); //通過stream().parallel()方法創(chuàng)建一個(gè)并行流,使用map()方法將每個(gè)字符串轉(zhuǎn)換為截取前7位的字符,最后使用collect()方法將結(jié)果收集到一個(gè)新列表中 List<String> filNums = numbers.stream().parallel().map(s -> s.substring(0,7)).collect(Collectors.toList()); System.out.println(filNums); //打印結(jié)果:[1337836, 1327824, 1317859, 1355812]
3.3、并行流的注意事項(xiàng):并行流可能引發(fā)的線程安全,以及如何避免這些問題
在使用并發(fā)流的過程中,可能會(huì)引發(fā)以下線程安全問題:并行流中的每個(gè)子集都在不同線程運(yùn)行,可能會(huì)導(dǎo)致對(duì)共享狀態(tài)的競(jìng)爭(zhēng)和沖突。
避免線程問題的方法如下:避免修改共享狀態(tài),即在處理集合過程當(dāng)中,避免被其他線程修改集合數(shù)據(jù),可以使用鎖來保證線程安全。
使用無狀態(tài)操作:在并行流處理過程盡量使用無狀態(tài)操作,例如filter、map之類的,可以盡量避免線程安全和同步問題。
四、Optional
4.1、什么是 Optional:Optional 類型的作用和使用場(chǎng)景
在實(shí)際開發(fā)當(dāng)中,Optional類型通常用于返回可能為空的方法、避免null值的傳遞和簡化復(fù)雜的判斷邏輯等場(chǎng)景。調(diào)用Optional對(duì)象的方法,需要通過isPresent()方法判斷值是否存在,如果存在則可以通過get()方法獲取其值,如果不存在則可以通過orElse()方法提供默認(rèn)值,或者拋出自定義異常處理。
4.2、如何使用 Optional:如何使用 Optional 類型
使用Optional類型主要目的是在數(shù)據(jù)可能為空的情況下,提供一種更安全、更優(yōu)雅的處理方式。
以下是使用Optional類型的常用方法:
4.2.1、ofNullable()和isPresent()方法
將一個(gè)可能為null的對(duì)象包裝成Optional類型的對(duì)象,然后根據(jù)isPresent方法判斷對(duì)象是否包含空值——
String str = null; Optional<String> optStr = Optional.ofNullable(str); if (optStr.isPresent()){ System.out.println("Optional對(duì)象不為空"); }else { System.out.println("Optional對(duì)象為空"); } //打印結(jié)果:Optional對(duì)象為空
4.2.2、get()方法
獲取Optional對(duì)象中的值,如果對(duì)象為空則拋出NoSuchElementException異常——
String str = null; Optional<String> optStr = Optional.ofNullable(str); if (optStr.isPresent()){ System.out.println("Optional對(duì)象不為空"); }else { System.out.println("Optional對(duì)象為空"); optStr.get(); }
?控制臺(tái)打印結(jié)果:
Exception in thread "main" java.util.NoSuchElementException: No value present
at java.util.Optional.get(Optional.java:135)
at com.zhu.fte.biz.test.StreamTest.main(StreamTest.java:144)
Optional對(duì)象為空
4.2.4、orElse()方法
獲取Optional對(duì)象中的值,如果對(duì)象為空則返回指定的默認(rèn)值——
String str = null; Optional<String> optStr = Optional.ofNullable(str); if (optStr.isPresent()){ System.out.println("Optional對(duì)象不為空"); }else { System.out.println("Optional對(duì)象為空,返回默認(rèn)值:" + optStr.orElse("null")); } //打印結(jié)果:Optional對(duì)象為空,返回默認(rèn)值:null
當(dāng)然,如果不為空的話,則能正常獲取對(duì)象中的值——
String str = "測(cè)試"; Optional<String> optStr = Optional.ofNullable(str); if (optStr.isPresent()){ System.out.println("Optional對(duì)象不為空,返回值:" + optStr.orElse("null")); }else { System.out.println("Optional對(duì)象為空,返回默認(rèn)值:" + optStr.orElse("null")); } //打印結(jié)果:Optional對(duì)象不為空,返回值:測(cè)試
那么,問題來了,它是否能判斷“ ”這類空格的字符串呢,我實(shí)驗(yàn)了一下,
String str = " "; Optional<String> optStr = Optional.ofNullable(str); if (optStr.isPresent()){ System.out.println("Optional對(duì)象不為空,返回值:" + optStr.orElse("null")); }else { System.out.println("Optional對(duì)象為空,返回默認(rèn)值:" + optStr.orElse("null")); } //打印結(jié)果:Optional對(duì)象不為空,返回值:
可見,這類空字符串,在orElse判斷當(dāng)中,跟StringUtils.isEmpty()類似,都是把它當(dāng)成非空字符串,但是StringUtils.isBlank()則判斷為空字符串。
4.2.5、orElseGet()方法
orElseGet()和orElse()類似,都可以提供一個(gè)默認(rèn)值。兩者區(qū)別在于,orElse方法在每次調(diào)用時(shí)都會(huì)創(chuàng)建默認(rèn)值,而orElseGet只在需要時(shí)才會(huì)創(chuàng)建默認(rèn)值。
4.3、Optional 和 null 的區(qū)別: Optional 類型與 null 值的異同
兩者都可以表示缺失值的情況,兩者主要區(qū)別為:Optional類型是一種包裝器對(duì)象,可以將一個(gè)可能為空的對(duì)象包裝成一個(gè)Optional對(duì)象。這個(gè)對(duì)象可以通過調(diào)用ofNullable()
、of()
或其他方法來創(chuàng)建。而null值則只是一個(gè)空引用,沒有任何實(shí)際的值。
Optional類型還可以避免出現(xiàn)NullPointerException異常,具體代碼案例如下:
String str = null; //錯(cuò)誤示范:直接調(diào)用str.length()方法會(huì)觸發(fā)NullPointerException //int length = str.length() //通過Optional類型避免NullPointerException Optional<String> optionalStr = Optional.ofNullable(str); if (optionalStr.isPresent()){//判斷Optional對(duì)象是否都包含非空值 int length = optionalStr.get().length(); System.out.println("字符串長度為:" + length); }else { System.out.println("字符串為空!"); } //使用map()方法對(duì)Optional對(duì)象進(jìn)行轉(zhuǎn)換時(shí),確保返回對(duì)結(jié)果不為null Optional<Integer> optionalLength = optionalStr.map(s -> s.length()); System.out.println("字符串長度為:" + optionalLength.orElse(-1)); // 使用orElse()方法提供默認(rèn)值
五、擴(kuò)展流處理
除里以上常用的流處理之外,Java8還新增了一些專門用來處理基本類型的流,例如IntStream、LongStream、DoubleStream等,其對(duì)應(yīng)的Api接口基本與前面案例相似,讀者可以自行研究。
最后,需要注意一點(diǎn)是,在流處理過程當(dāng)中,盡量使用原始類型數(shù)據(jù),避免裝箱操作,因?yàn)檠b箱過程會(huì)有性能開銷、內(nèi)存占用等問題,例如,當(dāng)原始數(shù)據(jù)int類型被裝箱成Integer包裝類型時(shí),這個(gè)過程會(huì)涉及到對(duì)象的創(chuàng)建、初始化、垃圾回收等過程,需要額外的性能開銷。
以上就是一文帶你深入了解Java8 Stream流式編程的詳細(xì)內(nèi)容,更多關(guān)于Java8 Stream流式編程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot+redis 實(shí)現(xiàn)分布式限流令牌桶的示例代碼
這篇文章主要介紹了springboot+redis 實(shí)現(xiàn)分布式限流令牌桶 ,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04集合框架(Collections Framework)詳解及代碼示例
這篇文章主要介紹了集合框架(Collections Framework)詳解及代碼示例,文章涉及集合數(shù)組的區(qū)別,collection接口,iterator迭代器,list接口及其用法,LinkedHashSet集合等有關(guān)內(nèi)容,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11在springboot中實(shí)現(xiàn)個(gè)別bean懶加載的操作
這篇文章主要介紹了在springboot中實(shí)現(xiàn)個(gè)別bean懶加載的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-10-10使用Cloud?Studio構(gòu)建SpringSecurity權(quán)限框架(騰訊云?Cloud?Studio?實(shí)戰(zhàn)訓(xùn)練
隨著云計(jì)算技術(shù)的成熟和普及,傳統(tǒng)編程能力和資源以云服務(wù)的形式開放出來,從中間件、數(shù)據(jù)庫等水平能力服務(wù)組件到人臉識(shí)別、鑒權(quán)服務(wù)等基本業(yè)務(wù)服務(wù)組件很容易的在云端獲取,本文介紹使用Cloud?Studio構(gòu)建SpringSecurity權(quán)限框架的相關(guān)知識(shí),感興趣的朋友一起看看吧2023-08-08如何使用@AllArgsConstructor和final 代替 @Autowired
這篇文章主要介紹了使用@AllArgsConstructor和final 代替 @Autowired方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09