標籤:lock dal 可變 結構 類型 params 內部類 java8 keyword
Collector介紹
Java8的stream api能很方便我們對資料進行統計分類等工作,函數式編程的風格讓我們方便並且直觀地編寫統計代碼。
例如:
Stream<Integer> stream = Stream.iterate(1, item -> item+2).limit(6);// stream.filter(item -> item > 2).mapToInt(item -> item * 2).skip(2).limit(2).sum();//.reduce(0, (val, val2)->val+val2);// System.out.println(sum);IntSummaryStatistics summaryStatistics = stream.filter(item -> item > 2).mapToInt(item -> item * 2).skip(2).summaryStatistics();System.out.println(summaryStatistics.getAverage());
stream裡有一個collect(Collector c)方法,這個方法裡面接收一個Collector的執行個體。這裡我們要弄清楚Collector與Collectors之間的關係。
作為collect方法的參數,Collector是一個介面,它是一個可變的匯聚操作,將輸入元素累計到一個可變的結果容器中;它會在所有元素都處理完畢後,將累積的結果轉換為一個最終的表示(這是一個可選操作);
這些如果你不太懂,請繼續往下看,結合下面自訂Collector,相信你可以理解這些內容。
Collectors本身提供了關於Collector的常見匯聚實現,Collectors的內部類CollectorImpl實現了Collector介面,Collectors本身實際上是一個工廠。
Collector的使用
很多時候我們會用到Collectors的toList方法,Collectors中提供了將流中元素累積到匯聚結果的各種方式,例如Counting、Joining、maxBy等等。下面是一個例子:
//分組 List<Student> list1 = Arrays.asList(s1, s2, s3, s4, s5); //根據名稱分組// Map<String, List<Student>> map = list1.stream().collect(Collectors.groupingBy(Student::getName)); //先根據名稱分組,然後求每組平均分 Map<String, Double> map = list1.stream().collect(Collectors.groupingBy(Student::getName, Collectors.averagingDouble(Student::getScore))); System.out.println(map); //分區 Map<Boolean, List<Student>> map1 = list1.stream().collect(Collectors.partitioningBy(item -> item.getScore() >= 90)); System.out.println(map1); System.out.println("------2-----"); //先根據名稱分組再根據分數分組 Map<String, Map<Integer, List<Student>>> map2 = list1.stream().collect(Collectors.groupingBy(Student::getName, Collectors.groupingBy(Student::getScore))); System.out.println(map2);
這裡分區分組與資料庫中的分區分組的概念類似。
Collectors are designed to be composed; many of the methods in {@link Collectors} are functions that take a collector and produce a new collector.
附上javadoc上的一句話,這句話說明收集操作是可以嵌套的。
自訂Collector
前面講過,Collectors本身提供了關於Collector的常見匯聚實現,那麼程式員自身也可以根據情況定義自己的匯聚實現。
首先我們看下Collector介面的結構
public interface Collector<T, A, R> { Supplier<A> supplier(); BiConsumer<A, T> accumulator(); BinaryOperator<A> combiner(); Function<A, R> finisher(); Set<Characteristics> characteristics();}
其中這裡的泛型所表示的含義是:
T:表示流中每個元素的類型。
A:表示中間結果容器的類型。
R:表示最終返回的結果類型。
Collector中還定義了一個枚舉類Characteristics,有三個枚舉值,理解這三個值的含義對於我們自己編寫正確的收集器也是至關重要的。
- Characteristics.CONCURRENT:表示中間結果只有一個,即使在並行流的情況下。所以只有在並行流且收集器不具備CONCURRENT特性時,combiner方法返回的lambda運算式才會執行(中間結果容器只有一個就無需合并)。
- Characteristics.UNORDER:表示流中的元素無序。
- Characteristics.IDENTITY_FINISH:表示中間結果容器類型與最終結果類型一致,此時finiser方法不會被調用。
我們再來看一下Collectors中toList方法的實現。
public static <T> Collector<T, ?, List<T>> toList() { return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add, (left, right) -> { left.addAll(right); return left; }, CH_ID); }static final Set<Collector.Characteristics> CH_ID = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
到此,不知你是否對自訂收集器有那麼點感覺了?
那麼到底怎麼實現自訂收集器呢,下面舉例子來看看。
public class MyCollectorImpl<T> implements Collector<T, Set<T>, Set<T>> { @Override public Supplier<Set<T>> supplier() { return HashSet<T>::new; } @Override public BiConsumer<Set<T>, T> accumulator() { return Set<T>::add; } @Override public BinaryOperator<Set<T>> combiner() { return (set, item) -> {set.addAll(item); return set;}; } @Override public Function<Set<T>, Set<T>> finisher() { return Function.identity(); } @Override public Set<Characteristics> characteristics() { return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH,UNORDERED)); } public static void main(String[] args) { List<String> list = Arrays.asList("hello", "world", "welcome"); Set<String> set = list.stream().collect(new MyCollectorImpl<>()); set.forEach(System.out::println); }}
這是一個簡單的例子,我們給收集器加上了IDENTITY_FINISH特性,此時finisher方法返回的lambda運算式是不會得到調用的。這一點也可以從源碼中得到驗證。
例2:
public class MyCollectorImpl2<T> implements Collector<T, Set<T>, Map<T, T>>{ @Override public Supplier<Set<T>> supplier() { return HashSet<T>::new; } @Override public BiConsumer<Set<T>, T> accumulator() { return Set<T>::add; } @Override public BinaryOperator<Set<T>> combiner() { return (set1, set2)->{ set1.addAll(set2); return set1; }; } @Override public Function<Set<T>, Map<T, T>> finisher() { return (set)->{ HashMap<T, T> map = new HashMap(); set.stream().forEach((item)->map.put(item, item)); return map; }; } @Override public Set<Characteristics> characteristics() { return Collections.unmodifiableSet(EnumSet.of(Characteristics.UNORDERED)); } public static void main(String[] args) { List<String> list = Arrays.asList("hello", "world", "welcome"); HashSet<String> set = new HashSet<>(); set.addAll(list); Map<String, String> map = set.stream().collect(new MyCollectorImpl2<>()); System.out.println(map); }}
這個例子中由於中間累計結果容器的類型與最終類型不一致,在finisher方法中必須做正確的處理,否則肯定拋出類型轉換的異常。
Java8中重要的收集器Collector