8. 將Stream內的資料收集起來

8.1 toArray()

toArray()會回傳一個 Object[]陣列,如果想得到其它型態的陣列,可使用如 toArray(String[]::new) 或 toArray(Integer[]::new)

8.2 collect() & Collectors

collect()方法可以將元素收集成我們要的集合,基本用法為 stream.collect(supplier, accumulator, combiner):


//假設有一個含有String的stream,想將它收集為List<String>
stream.collect(()-> new ArrayList<>(), (x, y) -> x.add(y), (x, y) -> x.addAll(y));

//簡化,利用方法引用
stream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);

//再簡化,利用現成的收集器
stream.collect(Collectors.toList());

//收集為Set
stream.collect(Collectors.toSet());

//自已決定集合
stream.collect(Collectors.toCollection(TreeSet::new));

//收集連接為字串
stream.collect(Collectors.join());
stream.collect(Collectors.join(","));

如果想得到數字統計相關的結果,可利用Collectors.summarizingInt()或averagingInt()等方法。


//取得數字統計相關資料
IntSummaryStatistics summary = ss.collect(Collectors.summarizingInt(String::length));
summary.getAverage();
summary.getSum();
summary.getMax();

//也可利用別的收集器,取得特定的數字統計
double avg = ss.collect(Collectors.averagingInt(String::length));

Collectors已有許多現成的收集器,可幫助我們收集成Map、Set、List、String...等。例如將字串元素收集成Map:


Map<String, Integer> map = stream.collect(Collectors.toMap(Function.identity(), String::length));

//將區域與語系資料收集為Map,注意toMap()的第三個參數,是告訴收集器有相同的key時如何處理
Map<String, String> langMap = Stream.of(Locale.getAvailableLocales())
                .collect(Collectors.toMap(x->x.getDisplayCountry(Locale.TAIWAN), x -> x.getDisplayLanguage(Locale.US), (oldValue, newValue) -> String.join(",", oldValue, newValue)));

練習:打開exec08.CollectData,將資料收集為Map<name, id>,重複的id使用「,」連接起來。

練習:打開exec08.PasswordGenerator,寫一個密碼產生器

8.3 Stream的分類

要將Stream中的元素分類成Map的話,可以使用Collectors.groupingBy(),例如要將單字依第一個字母來分類:


String st = "Apple, Asus, Alibaba, Orange, Microsoft, Google";
//預設收集為List
Map<Character, List<String>> tt = Stream.of(st.split("[\\s,]+")).collect(Collectors.groupingBy(x -> x.charAt(0)));

//也可指定收集為Set
Map<Character, Set<Stringg>> tt2 = Stream.of(st.split("[\\s,]+")).collect(Collectors.groupingBy(x -> x.charAt(0), Collectors.toSet()));

如果只是單純的依TRUE或FALSE分成二種,可改用Collectors.partitioningBy(),效率會較好,例如將單字分為A開頭及不是A開頭的:


Map<Boolean, List<String>> tt3 = Stream.of(st.split("[\\s,]+")).collect(Collectors.partitioningBy(x -> x.startsWith("A")));

練習:打開exec08.GroupData,找出每個傳統分區所包含的行政區數量。

練習:找出每個傳統分區的總人口數。

練習:找出每個傳統分區中,有著最多人口的行政區。

練習:找出每個傳統分區中,人口密度最大的行政區。

練習:將Map中每個傳統分區的行政區蛙稱,用「,」連接起來,作為Map的元素。