Java8學習筆記(十)--自定義收集器


前言

以前寫過Java8中的自定義收集器,當時只是在文章末尾放了個例子,覺得基本用法挺簡單,而且有些東西沒搞懂(比如combiner方法到底做什么的),沒有專門寫,過了一段時間又忘了,所以,即使還是沒搞懂combiner方法,還是硬着頭皮把使用的經驗記錄下,方便以后參考。

簡介

要實現自定義收集器,只需要實現java.util.stream.Collector<T, A, R>接口即可,這個接口包含五個無參方法:[supplier, accumulator, combiner, finisher, characteristics]。

泛型簡介

泛型含義如下:

  • T:縮減操作的輸入元素的類型
  • A:還原操作的可變累積類型(通常隱藏為實現細節)
  • R:還原操作的結果類型

T不必說,收集什么泛型的列表,就輸入什么類型,比如我對一個Student列表進行收集計算,那么T肯定是Student。
A是計算過程中用來盛放計算結果的容器,一般都是List,Set等等。
R就比較好理解,就是收集完成后返回的類型,需要注意的是,當characteristics()中包含Characteristics.IDENTITY_FINISH時,。

方法簡介

  • characteristics 表示收集計算的方式,返回類型為Set<Characteristics>,其中Characteristics是一個枚舉類型,指示收集器屬性的特征,可用於優化縮減實現。它只有三個值[CONCURRENT, UNORDERED, IDENTITY_FINISH],注釋翻譯過來分別是:1.表示此收集器是並發的,這意味着結果容器可以支持與來自多個線程的相同結果容器同時調用的累加器函數。如果CONCURRENT收集器也不是UNORDERED,那么只有在應用於無序數據源時才應同時評估它。2.指示集合操作不承諾保留輸入元素的遭遇順序。(如果結果容器沒有內在順序,例如Set,則可能是這樣。)3.表示整理器功能是標識功能,可以省略。 如果設置,則必須是從A到R的未經檢查的強制轉換成功的情況。
  • supplier 該方法返回一個Supplier<A>類型的結果,表示在計算過程中,如何初始化一個臨時容器,比如A=List,那么一般返回ArrayList::new
  • accumulator 核心方法,關鍵的計算邏輯都放在這里,定義了如何把一個個元素放入臨時容器中,返回類型為BiConsumer<A, T>
  • combiner 返回一個BinaryOperator<A>類型的結果,個人理解是如何合並臨時容器,但是在實際應用中沒碰到執行過,所以一般直接返回null
  • finisher 定義了如何把臨時容器轉換為輸出結果,返回類型為Function<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>

需求1

有一個學生列表,求計算學生的總分

數據結構

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);

結果

需求2

將輸出結果轉化成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()方法即可,如下所示:

        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>>是一樣的,但看起來更簡潔明了!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM