之前寫過Java8中的自定義收集器,當時只是在文章末尾放了個例子,以爲基本用法挺簡單,並且有些東西沒搞懂(好比combiner方法到底作什麼的),沒有專門寫,過了一段時間又忘了,因此,即便仍是沒搞懂combiner方法,仍是硬着頭皮把使用的經驗記錄下,方便之後參考。java
要實現自定義收集器,只須要實現java.util.stream.Collector<T, A, R>
接口便可,這個接口包含五個無參方法:[supplier, accumulator, combiner, finisher, characteristics]。json
泛型含義以下:數據結構
T
沒必要說,收集什麼泛型的列表,就輸入什麼類型,好比我對一個Student列表進行收集計算,那麼T確定是Student。
A
是計算過程當中用來盛放計算結果的容器,通常都是List,Set等等。
R
就比較好理解,就是收集完成後返回的類型,須要注意的是,當characteristics()中包含Characteristics.IDENTITY_FINISH時,。併發
Set<Characteristics>
,其中Characteristics
是一個枚舉類型,指示收集器屬性的特徵,可用於優化縮減實現。它只有三個值[CONCURRENT, UNORDERED, IDENTITY_FINISH]
,註釋翻譯過來分別是:1.表示此收集器是併發的,這意味着結果容器能夠支持與來自多個線程的相同結果容器同時調用的累加器函數。若是CONCURRENT收集器也不是UNORDERED,那麼只有在應用於無序數據源時才應同時評估它。2.指示集合操做不承諾保留輸入元素的遭遇順序。(若是結果容器沒有內在順序,例如Set,則多是這樣。)3.表示整理器功能是標識功能,能夠省略。 若是設置,則必須是從A到R的未經檢查的強制轉換成功的狀況。Supplier<A>
類型的結果,表示在計算過程當中,如何初始化一個臨時容器,好比A=List,那麼通常返回ArrayList::newBiConsumer<A, T>
BinaryOperator<A>
類型的結果,我的理解是如何合併臨時容器,可是在實際應用中沒碰到執行過,因此通常直接返回nullFunction<A, R>
,須要注意的是,當方法characteristics
的返回值中包含Characteristics.IDENTITY_FINISH
,則必須保證從A到R可以強制轉換成功,此時該方法固定返回Function.identity()
便可。不然會報錯。好比A爲ArrayList,而R爲HashMap的狀況就會報錯。<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.33</version> </dependency>
有一個學生列表,求計算學生的總分ide
public class Student { private String id; private Course course; private double score; public Student() { } public Student(String id, Course course, double score) { this.id = id; this.course = course; this.score = score; } @Override public String toString() { return JSON.toJSONString(this, true); } public enum Course { LANGUAGE, MATHEMATICS, ENGLISH, TOTAL } // 省略setter、getter方法 }
public static final List<Student> students = List.of(new Student("1", Student.Course.ENGLISH, 78), new Student("1", Student.Course.LANGUAGE, 71), new Student("1", Student.Course.MATHEMATICS, 82), new Student("2", Student.Course.ENGLISH, 69), new Student("2", Student.Course.LANGUAGE, 66), new Student("2", Student.Course.MATHEMATICS, 46), new Student("3", Student.Course.ENGLISH, 78), new Student("3", Student.Course.LANGUAGE, 88), new Student("3", Student.Course.MATHEMATICS, 100), new Student("4", Student.Course.ENGLISH, 68), new Student("4", Student.Course.LANGUAGE, 84), new Student("4", Student.Course.MATHEMATICS, 90), new Student("5", Student.Course.ENGLISH, 74), new Student("5", Student.Course.LANGUAGE, 59), new Student("5", Student.Course.MATHEMATICS, 87));
public class StudentCollector implements Collector<Student, List<Student>, List<Student>> { @Override public Supplier<List<Student>> supplier() { return ArrayList::new; } @Override public BiConsumer<Set<Student>, Student> accumulator() { return (set, student) -> { Predicate<Student> studentPredicate = s -> s.getId().equals(student.getId()); boolean b = set.stream().noneMatch(studentPredicate); if (b) { student.setCourse(Student.Course.TOTAL); set.add(student); } set.stream().filter(studentPredicate).forEach(s -> s.setScore(s.getScore() + student.getScore())); }; } @Override public BinaryOperator<List<Student>> combiner() { return null; } @Override public Function<List<Student>, List<Student>> finisher() { return Function.identity(); } @Override public Set<Characteristics> characteristics() { return EnumSet.of(Characteristics.IDENTITY_FINISH); } }
List<Student> studentList = students.stream().collect(new StudentCollector()); System.out.println(studentList);
將輸出結果轉化成TreeMap<Double, Student>
,key爲學生總分,value爲學生自己,並按照總分從高到低排序。函數
數據結構與學生列表不變。優化
public class StudentDiffCollectors implements Collector<Student, Set<Student>, TreeMap<Double, Student>> { @Override public Supplier<Set<Student>> supplier() { return HashSet::new; } @Override public BiConsumer<Set<Student>, Student> accumulator() { return (set, student) -> { Predicate<Student> studentPredicate = s -> s.getId().equals(student.getId()); boolean b = set.stream().noneMatch(studentPredicate); if (b) { student.setCourse(Student.Course.TOTAL); set.add(student); } set.stream().filter(studentPredicate).forEach(s -> s.setScore(s.getScore() + student.getScore())); }; } @Override public BinaryOperator<Set<Student>> combiner() { return null; } @Override public Function<Set<Student>, TreeMap<Double, Student>> finisher() { return list -> { TreeMap<Double, Student> doubleStudentTreeMap = new TreeMap<>(Comparator.reverseOrder()); list.forEach(student -> doubleStudentTreeMap.put(student.getScore(), student)); return doubleStudentTreeMap; }; } @Override public Set<Characteristics> characteristics() { return EnumSet.of(Characteristics.CONCURRENT, Characteristics.UNORDERED); } }
TreeMap<Double, Student> collect = students.stream().collect(new StudentDiffCollectors()); System.out.println(collect);
除了實現接口之外,jdk還幫咱們提供了更簡單的實現,只須要調用Collector.of()
方法便可,以下所示:this
Collector<Student, List<Student>, List<Student>> studentCollector = Collector.of(ArrayList::new, (l, r) -> { // 計算邏輯 }, (l, r) -> null, Function.identity(), Collector.Characteristics.IDENTITY_FINISH);
因爲它符合A強轉爲R的條件,所以,能夠更簡化爲:線程
Collector<Student, List<Student>, List<Student>> studentCollector = Collector.of(ArrayList::new, (l, r) -> { // 計算邏輯 }, (l, r) -> null);
它的效果和實現接口Collector<Student, List<Student>, List<Student>>
是同樣的,但看起來更簡潔明瞭!翻譯