架構(gòu)如果Spring家族沒有推出Spring Boot,Spring是有被取代風(fēng)險的,因?yàn)槟菚r的開發(fā)者對它的配置繁瑣、使用曲線較高已有所反感(即使比EJB還輕太多)。
??前言
讀過《Java核心技術(shù)》的同學(xué)可能記得里面有一句話:“注意不要編寫返回引用可變對象的訪問器方法”。對于這句話,筆者在和同事交流常表示:若你遇見有同事代碼能這么寫的,一定值得你的高看(雖不一定有實(shí)際作用),因?yàn)檫@就是coding sense,相對稀有。
本文討論的議題是:Stream流返回集合時,是否可以繼續(xù)向此集合add元素?
?正文
Java 8的Stream流有兩大特點(diǎn):
- 不可變:不影響原集合,每次調(diào)用都返回一個新的Stream
- 延遲執(zhí)行:在遇到終結(jié)操作之前,Stream不會執(zhí)行
這里面有個“不可變”,針對于這里它有兩重含義:
- 每次操作都會生成一個新的Stream。因此Stream是不可變的(就像LocalDate、String等)
- 原集合不受影響。在進(jìn)行數(shù)據(jù)操作時,不會對原來的集合元素有影響
總之,在使用Stream時,我們不用關(guān)心操作對原集合帶來的“副作用”,非常省心。
toList()/toSet()返回的集合類型
Stream操作最常用的莫過于toList()和toSet()兩個Collector收集方式,看看返回的是什么類型勒。
toList()
@Test
public void fun() {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
List<Integer> streamResultForList = list.stream().collect(toList());
System.out.println("toList()返回的類型:" + streamResultForList.getClass());
System.out.println(streamResultForList.getClass() == list.getClass());
}
運(yùn)行程序,輸出:
toList()返回的類型:class java.util.ArrayList
true
toSet()
@Test
public void fun1() {
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(3);
Set<Integer> streamResultForSet = set.stream().collect(toSet());
System.out.println("toSet()返回的類型:" + streamResultForSet.getClass());
System.out.println(streamResultForSet.getClass() == set.getClass());
}
運(yùn)行程序,輸出:
toSet()返回的類型:class java.util.HashSet
true
結(jié)論:
- toList()返回的是ArrayList類型
- toSet()返回的是HashSet類型
原理
已經(jīng)知道了返回類型,就順勢再走近一點(diǎn),看看為啥返回的是這個結(jié)果呢?
其實(shí)僅僅只需向前一小步,點(diǎn)進(jìn)去源碼一看便知:


標(biāo)題問題的答案
可以。不管是toList()還是toSet()返回的都是咱最常用的集合類型,所以肯定可以add元素呀。
@Test
public void fun2() {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
System.out.println(list.getClass());
List<Integer> streamResultForList = list.stream().collect(toList());
streamResultForList.add(10);
System.out.println("stream后的集合:" + streamResultForList);
System.out.println("源集合:" + list);
}
運(yùn)行程序,輸出:
class java.util.ArrayList
stream后的集合:[1, 2, 3, 10]
源集合:[1, 2, 3]
stream后的集合成功添加了元素10,但源集合不受影響哦。
如何返回不可變集合
返回不可變引用(對象、集合)是提高程序健壯性的有效手段之一,那么如何做到返回一個不可變(或者線程安全)的集合呢?
其實(shí),上面的截圖里,JDK已經(jīng)給了我們答案:

簡而言之:如果希望返回自己控制的結(jié)合類型,請使用toCollection(Supplier)收集器,具體返回什么樣的集合,交給使用者實(shí)現(xiàn)Supplier。
這里,筆者通過三種方式用三種方式來返回不可變集合類型,供你參考:
方式一:直接提供一個不可變集合的實(shí)例
@Test
public void fun4() {
List<Integer> list = new ArrayList<>();
Collection<Integer> streamCollection = list.stream().collect(toCollection(()-> Arrays.asList()));
System.out.println("stream后的集合類型:" + streamCollection.getClass());
streamCollection.add(10);
}
運(yùn)行結(jié)果:
stream后的集合類型:class java.util.Arrays$ArrayList
java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
方式二:間接提供一個不可變集合的實(shí)例
@Test
public void fun5() {
List<Integer> list = new ArrayList<>();
Collection<Integer> streamCollection = list.stream().collect(toCollection(() -> Collections.unmodifiableList(new ArrayList<>())));
System.out.println("stream后的集合類型:" + streamCollection.getClass());
streamCollection.add(10);
}
運(yùn)行結(jié)果:
stream后的集合類型:class java.util.Collections$UnmodifiableRandomAccessList
java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableCollection.add(Collections.java:1057)
方式三:使用collectingAndThen分布進(jìn)行(推薦)
@Test
public void fun6() {
List<Integer> list = new ArrayList<>();
Collection<Integer> streamCollection = list.stream()
.collect(collectingAndThen(toSet(), l -> Collections.unmodifiableSet(l)));
System.out.println("stream后的集合類型:" + streamCollection.getClass());
streamCollection.add(10);
}
運(yùn)行結(jié)果:
stream后的集合類型:class java.util.Collections$UnmodifiableSet
java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableCollection.add(Collections.java:1057)
給你留哥問題:返回線程安全的集合類型,你會了嗎?
??總結(jié)
本文除了弄清楚標(biāo)題所描述的問題外,另一目的是建議coder們都能有引用類型“不可變”的意識(當(dāng)然后面有專文分享這個話題),代碼水平或許就是這樣一步步提升的,積跬步方可至千里。
本專欄源代碼庫:https://github.com/yourbatman/yourbatman-999-question
- 個人博客:https://yourbatman.cn
- 程序員網(wǎng)盤:https://wangpan.yourbatman.cn
- 女媧工程:https://start.yourbatman.cn
- 更多專欄:??https://yourbatman.cn/columns?? |或| 公號后臺回復(fù)“專欄列表”獲取全部小而美的原創(chuàng)技術(shù)專欄
我是YourBatman,一個俗人,貪財好色。歷經(jīng)過延期畢業(yè)、賣保險、送外賣的大齡程序員,《夢幻西游》骨灰玩家;龍珠迷、火影迷?,F(xiàn)資深領(lǐng)域建模專家、Java架構(gòu)師;高質(zhì)量代碼、DDD面向?qū)ο笤O(shè)計布道師;Spring開源貢獻(xiàn)者,CSDN博客之星年度Top 10,出版書籍《Spring奇淫巧技》&《領(lǐng)域建模之面向?qū)ο蟪绦蛟O(shè)計》進(jìn)行時。