這裡先提一個題外話,如果想看 JDK 不同版本之間有何差異,增加或者刪除了哪些 API,可以通過下面這個連結檢視:
路徑中的兩個版本就是要對比的兩個版本,其介面如下:
同時,我們也可以通過 JDK 內建 jdeps 工具查詢過期以及廢棄API以及對應的替換
jdeps --jdk-internals -R --class-path 'libs/*' $project
libs是你的所有依賴的目錄,$project是你的專案jar包,示例輸出:
...
JDK Internal API Suggested Replacement
---------------- ---------------------
sun.misc.BASE64Encoder Use java.util.Base64 @since 1.8
sun.reflect.Reflection Use java.lang.StackWalker @since 9
Java 16 中針對 Stream API 有兩個更新:
增加了 mapMulti
和 toList
這兩個 API。
mapMulti
mapMulti
其實主要是對現有的 flatMap
在某些場景使用起來不夠合適的補充。flatMap
是將一個物件對映為多個物件之後繼續 Stream,例如將 List<List<Integer>>
裡面的每一個數字取出,轉換成一個新的 List<Integer>
:
integerLists.stream().flatMap(integers -> integers.stream()).collect(Collectors.toList());
這對於每個元素本身就是集合型別的場景來說,非常適用。我們再來看一個例子,假設有郵件這個 Record 類,包含 id,以及傳送到的郵箱和抄送到的郵箱:
record Mail(int id, Set<String> sendTo, Set<String> cc) {}
我們想找到一批郵件的所有不同的聯絡人,最後放到一個 List 中,可能會這麼寫:
Set<String> collect = mails.stream().flatMap(mail -> {
Set<String> result = new HashSet<>();
result.addAll(mail.sendTo());
result.addAll(mail.cc());
return result.stream();
}).collect(Collectors.toSet());
但是,這樣寫顯然很不優雅,首先是對於每一個 Mail 都建立了額外的 Set 和對應的 Stream,並且,對於每個 mail 的 sendTo 還有 cc 都遍歷了兩遍(addAll 一遍,後續 Stream 又一遍)。其實我們的目前只是將 mail 中的 cc 以及 sendTo 取出來,用於參與後續的 Stream。在這種場景下,就非常適合用 mapMulti:
Set<String> collect = mails.stream().<String>mapMulti((mail, consumer) -> {
mail.cc().forEach(consumer::accept);
mail.sendTo().forEach(consumer::accept);
}).collect(Collectors.toSet());
可以看出:
- mapMulti 的入參是一個
BiConsumer
,其實就是使用其引數中的 consumer 接收參與 Stream 後續的物件 - mapMulti 的思路就是將引數中的需要參與後續 Stream 的物件傳入 consumer 來繼續 Stream
- consumer 沒有限制物件型別,想要限制必須加上形參
<String>
否則最後返回的是Set<Object>
而不是Set<String>
假設 mail 的 sendTo 還有 cc 都需要去其他地方獲取,使用 mapMulti 還可以實現:
Set<String> collect = mailIds.stream().<String>mapMulti((mailId, consumer) -> {
mailService.getCCById(mailId).forEach(consumer::accept);
mailService.getSendToById(mailId).forEach(consumer::accept);
}).collect(Collectors.toSet());
還有一些比較有意思的用法,例如混合型別的 List 轉換成統一型別:
class C {
static void expandIterable(Object e, Consumer<Object> c) {
if (e instanceof Iterable<?> elements) {
for (Object ie : elements) {
expandIterable(ie, c);
}
} else if (e != null) {
c.accept(e);
}
}
public static void main(String[] args) {
var nestedList = List.of(1, List.of(2, List.of(3, 4)), 5);
Stream<Object> expandedStream = nestedList.stream().mapMulti(C::expandIterable);
}
}
活用 Optional.ifPresent(Consumer<? super T> action)
方法:
Stream.of(Optional.of("0"), Optional.of("1"), Optional.empty())
.mapMulti(Optional::ifPresent)
.forEach(System.out::print);
toList
對於 Stream 增加了 toList 直接轉換成 List,由於不涉及 collect 裡面的截斷操作,所以比 collect 佔用的記憶體更小,需要的操作更少並且更快。
之前轉換成 List,需要 collect(Collectors.toList())
,生成的 List 是 ArrayList
,是可變的
但是這次新加的 Api,toList 生成的是 UnmodifiableList
,是不可變的。
所以這兩個 API 不能直接互相替換,需要做一些檢查確認沒有更改才能替換。
微信搜尋“我的程式設計喵”關注公眾號,每日一刷,輕鬆提升技術,斬獲各種offer: