1. 程式人生 > >lambda表示式-高階集合類和收集器

lambda表示式-高階集合類和收集器

lambda表示式提供了很多的集合類和收集器來簡化程式設計,使之更加方便和美觀,所以這裡介紹一些常用的集合類和收集器來處理繁雜的程式碼。

1、方法引用:形如 User::getName,TreeSet::new 等價於user.getName(), new TreeSet<>();

可以引用靜態方法、例項物件方法、構造方法等。

    /**
     * 方法引用
     */
    @Test
    public void refTest() {
        List<String> collect = list.stream().map(u -> u.getName()).collect(Collectors.toList());
        System.out.println(collect);
        // 等價於 User::getName
        System.out.println(list.stream().map(User::getName).collect(Collectors.toList()));

        // TreeSet::new
        Stream<Integer> integerStream = Stream.of(1, 2, 3);
        TreeSet<Integer> integerTreeSet = integerStream.collect(Collectors.toCollection(TreeSet::new));
        System.out.println(integerTreeSet);
    }

2、流中元素的順序,如果集合本身無序,則轉換的流仍然無序,如HashSet(進入順序)

    /**
     * 方法引用
     */
    @Test
    public void refTest() {
        List<String> collect = list.stream().map(u -> u.getName()).collect(Collectors.toList());
        System.out.println(collect);
        // 等價於 User::getName
        System.out.println(list.stream().map(User::getName).collect(Collectors.toList()));

        // TreeSet::new
        Stream<Integer> integerStream = Stream.of(1, 2, 3);
        TreeSet<Integer> integerTreeSet = integerStream.collect(Collectors.toCollection(TreeSet::new));
        System.out.println(integerTreeSet);
    }

3、maxBy(比較器),minBy(..), 找出最大最小值

    /**
     * 轉換成值
     */
    @Test
    public void biggestGroupTest() {
        Optional<Artist> optional = biggestGroup(artists.stream());
        System.out.println(optional.get().getName());  // -> ace
    }
    public Optional<Artist> biggestGroup(Stream<Artist> artists) {
        Function<Artist, Long> getCount = artist -> artist.getMembers().count();
        return artists.collect(maxBy(comparing(getCount))); // 找出最大成員數目
    }

4、averagingDouble(),求平均值

    /**
     * 找出一組專輯上曲目的平均數
     */
    @Test
    public void test() {
        System.out.println(averageNumberOfTracks(albums));
    }
    public double averageNumberOfTracks(List<Album> albums) {
        return albums.stream().collect(averagingDouble(album -> album.getTrackList().size()));
    }

5、資料分塊 partitioningBy,根據true, false分成兩塊資料

    /**
     * 資料分塊 partitioningBy, true or false值劃分, 分成true or false 兩部分
     */
    @Test
    public void partitioningByTest() {
        Map<Boolean, List<Artist>> booleanListMap = partitioningByNationality(artists.stream());
        List<Artist> trueList = booleanListMap.get(true);
        System.out.println(trueList);  // out -> each
        List<Artist> falseList = booleanListMap.get(false);
        System.out.println(falseList); // out -> ace
    }
    public Map<Boolean, List<Artist>> partitioningByNationality(Stream<Artist> artists) {
        // 將國籍分成中國和其他兩組
        return artists.collect(Collectors.partitioningBy(artist -> "china".equals(artist.getNationality())));
    }

6、資料分組 groupingBy(),可以根據任意值進行分組

    /**
     * 資料分組 groupingBy, 任意值劃分, 分成true or false 兩部分
     */
    @Test
    public void groupingByTest() {
        Map<String, List<Artist>> stringListMap = groupingByNationality(artists.stream());
        List<Artist> list = stringListMap.get("china");  // out -> each
        System.out.println(list);
        List<Artist> list1 = stringListMap.get("US");  // out -> ace
        System.out.println(list1);

        Map<Artist, List<Album>> streamListMap = groupingByArtist(albums.stream());
        System.out.println(streamListMap.get(artist1));  // 專輯1, 專輯2
    }
    public Map<String, List<Artist>> groupingByNationality(Stream<Artist> artists) {
        // 按照國籍分組
        return artists.collect(Collectors.groupingBy(artist -> artist.getNationality()));
    }
    /** 按照主唱分組 */
    public Map<Artist, List<Album>> groupingByArtist(Stream<Album> albums) {
        return albums.collect(groupingBy(album -> album.getMainMusician()));
    }

7、字串拼裝  Collectors.joining, 按照指定的格式拼裝字串

    /**
     * 輸出字串
     */
    @Test
    public void joiningTest() {
        String joiningName = joiningName(artists.stream());
        System.out.println(joiningName);  // out -> [ace,each]
    }
    public String joiningName(Stream<Artist> artists) {
        return artists.map(Artist::getName).collect(Collectors.joining(",", "[", "]"));
    }

8、單一的收集器已經能實現很多基礎的功能,組合方式更顯強大


    // 組合收集器

    /** ===== 下游收集器: 例子中用到的第二個收集器, 用來收集最終結果的一個子集 ===== */

    /**
     * 統計某個藝術家專輯數量
     */
    @Test
    public void combineTest() {
        long braden = numberOfAlbums(albums.stream(), "bradenlei");
        System.out.println(braden);
    }
    public long numberOfAlbums(Stream<Album> albums, String artistName) {
        return albums.map(album -> album.getMusicians())
                .filter(musicians -> musicians.filter(musician -> artistName.equals(musician.getName())).count() > 0)
                .count();
    }

    /**
     * 計算每個藝術家的專輯數(counting)
     */
    @Test
    public void combineTest2() {
        Map<Artist, Long> count = count(albums.stream());
        System.out.println(count.get(artist1)); // out -> 2
    }
    public Map<Artist, Long> count(Stream<Album> albums) {
        return albums.collect(groupingBy(album -> album.getMainMusician(), counting()));
    }

    /**
     * 收集每個藝術家的專輯名稱 (mapping)
     */
    @Test
    public void combineTest3() {
        Map<Artist, List<String>> artistListMap = collectName(albums.stream());
        System.out.println(artistListMap.get(artist1)); // out -> [專輯1, 專輯2]
    }
    public Map<Artist, List<String>> collectName(Stream<Album> alubms) {
        return alubms.collect(groupingBy(Album::getMainMusician, mapping(Album::getName, toList())));
    }

9、有些時候現有的收集器不能滿足我們的開發需求,當然也就可以重構和定製收集器了

    /** =====重構和定製收集器===== */

    /**
     * 使用除joining之外的方法拼接字串
     *
     * 獲取藝術家姓名 (初步)
     */
    @Test
    public void jointTest() {
        StringBuilder reduced = artists.stream()
                .map(Artist::getName)
                .reduce(new StringBuilder(), (builder, name) -> {
                    if (builder.length() > 0) builder.append(", ");

                    return builder.append(name);
                }, (left, right) -> left.append(right));
        reduced.insert(0, "[");
        reduced.append("]");
        String result = reduced.toString();

        System.out.println(result); // out -> [ace, each]
    }
    /**
     * 優化(自定義StringCombiner)
     */
    @Test
    public void jointTest2() {
        StringCombiner reduced = artists.stream()
                .map(Artist::getName)
                .reduce(new StringCombiner(", ", "[", "]"),
                        StringCombiner::add,
                        StringCombiner::merge); // 這裡實際沒有呼叫merge
        String string = reduced.toString();
        System.out.println(string);  // out -> [ace, each]
    }

    /***
     * 進一步優化(使用定製收集器StringCollector收集)
     */
     @Test
    public void jointTest3() {
         String collect = artists.stream()
                 .map(Artist::getName)
                 .collect(new StringCollector(", ", "[", "]"));
         System.out.println(collect); // out -> [ace, each]
     }

    /**
     * 用reducing代替自定義收集器
     */
    @Test
    public void jointtTest4() {
        // 這種方法的缺點是第二個引數每次都需要new一個物件
        StringCombiner collect = artists.stream()
                .map(Artist::getName)
                .collect(Collectors.reducing(
                        new StringCombiner(", ", "[", "]"),
                        name -> new StringCombiner(", ", "[", "]").add(name),
                        StringCombiner::merge));  // 這裡會呼叫merge, 每次都是兩個StringCombiner物件合併, 所以修改StringCombiner.add即可得到正確格式[ace, each]
        String s = collect.toString();
        System.out.println(s);  // out -> [ace[each]
    }

自定義類:

public class StringCombiner {
    // 字首
    private String prefix;
    // 分隔符
    private String delim;
    // 字尾
    private String suffix;

    private StringBuilder builder;

    public StringCombiner(String delim, String prefix, String suffix) {
        this.delim = delim;
        this.prefix = prefix;
        this.suffix = suffix;
        builder = new StringBuilder();
    }

    public StringCombiner add(String element) {
        if(builder.length() > 0) {
            builder.append(delim);
        } else {
            builder.insert(0, prefix);
        }
            builder.append(element);
        return this;
    }

    public StringCombiner merge(StringCombiner combiner) {
        builder.append(combiner.builder);
        return this;
    }

    @Override
    public String toString() {
        if ("".equals(suffix)) {
            return builder.toString();
        } else {
            int tmp = builder.length();
            String result = builder.append(suffix).toString();
            builder.setLength(tmp);
            return result;
        }
    }
}

其中:StringCombiner功能仿製java提供的StringJoiner

/**
 * String 待收集的元素字串
 * StringCombiner 累加器型別
 * String 最終結果型別
 */
public class StringCollector implements Collector<String, StringCombiner, String> {
    // 字首
    private String prefix;
    // 分隔符
    private String delim;
    // 字尾
    private String suffix;

    public StringCollector(String delim, String prefix, String suffix) {
        this.delim = delim;
        this.prefix = prefix;
        this.suffix = suffix;
    }
    @Override
    public Supplier<StringCombiner> supplier() {
        return () -> new StringCombiner(delim, prefix, suffix);
    }

    @Override
    public BiConsumer<StringCombiner, String> accumulator() {
        return StringCombiner::add;
    }

    @Override
    public BinaryOperator<StringCombiner> combiner() {
        return StringCombiner::merge;
    }

    @Override
    public Function<StringCombiner, String> finisher() {
        return StringCombiner::toString;
    }

    @Override
    public Set<Characteristics> characteristics() {
        Set<Collector.Characteristics> emptySet = Collections.emptySet();
        return emptySet;
    }
}

10、完整demo:

public class CollectorDemo {
    private static User user;
    private static List<User> list;

    @Before
    public void init() {
        if (user == null) {
            user = new User("張三", 11001);
        }
        if (list == null) {
            list = new ArrayList<>();
            list.add(new User("李四", 11002));
            list.add(new User("王五", 11003));
            list.add(new User("周莊", 11004));
        }
    }

    /**
     * 方法引用
     */
    @Test
    public void refTest() {
        List<String> collect = list.stream().map(u -> u.getName()).collect(Collectors.toList());
        System.out.println(collect);
        // 等價於 User::getName
        System.out.println(list.stream().map(User::getName).collect(Collectors.toList()));

        // TreeSet::new
        Stream<Integer> integerStream = Stream.of(1, 2, 3);
        TreeSet<Integer> integerTreeSet = integerStream.collect(Collectors.toCollection(TreeSet::new));
        System.out.println(integerTreeSet);
    }

    /**
     *  元素順序, 如果集合本身無序, 則轉換的流也是無序的如, HashSet
     */
    @Test
    public void orderTest() {
        // 進來的流無序, 則出去的流也是無序
        List<Integer> origin = Arrays.asList(1, 2, 3, 4);
        List<Integer> integerList = origin.stream().collect(Collectors.toList());
        assertEquals(origin, integerList); // ok

        Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
        Set<Integer> integerSet = set.stream().collect(Collectors.toSet());
        assertEquals(set, integerSet);  // 不能保證每次通過

        List<Integer> list = Arrays.asList(2, 1, 4, 3);
        List<Integer> collect = list.stream().sorted().collect(Collectors.toList());
        assertEquals(origin, collect); // ok
    }

    private static List<Artist> artists = null;
    private static List<Artist> artists2 = null;
    private static List<Track> tracks = null;
    private static List<Track> tracks2 = null;
    private static List<Album> albums = null;
    private static Artist artist1 = null;
    private static Artist artist2 = null;
    static {
        Artist artist = new Artist("braden", "china");
        artist1 = new Artist("ace", Arrays.asList(artist), "US");
        artist2 = new Artist("each", "china");
        artists = Arrays.asList(artist1, artist2);

        tracks = Arrays.asList(new Track("love you", 3), new Track("Shape of you", 5), new Track("夜上海", 4));
        tracks2 = Arrays.asList(new Track("喜歡你", 4), new Track("不分手的戀愛", 5));

        artists2 = Arrays.asList(new Artist("bradenlei","china"), new Artist("Mr.zhang","UK"));
        albums = Arrays.asList(new Album("專輯1", tracks, artists), new Album("專輯2", tracks2, artists), new Album("專輯3", tracks, artists2));
    }
    /**
     * 轉換成值
     */
    @Test
    public void biggestGroupTest() {
        Optional<Artist> optional = biggestGroup(artists.stream());
        System.out.println(optional.get().getName());  // -> ace
    }
    public Optional<Artist> biggestGroup(Stream<Artist> artists) {
        Function<Artist, Long> getCount = artist -> artist.getMembers().count();
        return artists.collect(maxBy(comparing(getCount))); // 找出最大成員數目
    }

    /**
     * 找出一組專輯上曲目的平均數
     */
    @Test
    public void test() {
        System.out.println(averageNumberOfTracks(albums));
    }
    public double averageNumberOfTracks(List<Album> albums) {
        return albums.stream().collect(averagingDouble(album -> album.getTrackList().size()));
    }

    /**
     * 資料分塊 partitioningBy, true or false值劃分, 分成true or false 兩部分
     */
    @Test
    public void partitioningByTest() {
        Map<Boolean, List<Artist>> booleanListMap = partitioningByNationality(artists.stream());
        List<Artist> trueList = booleanListMap.get(true);
        System.out.println(trueList);  // out -> each
        List<Artist> falseList = booleanListMap.get(false);
        System.out.println(falseList); // out -> ace
    }
    public Map<Boolean, List<Artist>> partitioningByNationality(Stream<Artist> artists) {
        // 將國籍分成中國和其他兩組
        return artists.collect(Collectors.partitioningBy(artist -> "china".equals(artist.getNationality())));
    }

    /**
     * 資料分組 groupingBy, 任意值劃分, 分成true or false 兩部分
     */
    @Test
    public void groupingByTest() {
        Map<String, List<Artist>> stringListMap = groupingByNationality(artists.stream());
        List<Artist> list = stringListMap.get("china");  // out -> each
        System.out.println(list);
        List<Artist> list1 = stringListMap.get("US");  // out -> ace
        System.out.println(list1);

        Map<Artist, List<Album>> streamListMap = groupingByArtist(albums.stream());
        System.out.println(streamListMap.get(artist1));  // 專輯1, 專輯2
    }
    public Map<String, List<Artist>> groupingByNationality(Stream<Artist> artists) {
        // 按照國籍分組
        return artists.collect(Collectors.groupingBy(artist -> artist.getNationality()));
    }
    /** 按照主唱分組 */
    public Map<Artist, List<Album>> groupingByArtist(Stream<Album> albums) {
        return albums.collect(groupingBy(album -> album.getMainMusician()));
    }

    /**
     * 輸出字串
     */
    @Test
    public void joiningTest() {
        String joiningName = joiningName(artists.stream());
        System.out.println(joiningName);  // out -> [ace,each]
    }
    public String joiningName(Stream<Artist> artists) {
        return artists.map(Artist::getName).collect(Collectors.joining(",", "[", "]"));
    }

    // 組合收集器

    /** ===== 下游收集器: 例子中用到的第二個收集器, 用來收集最終結果的一個子集 ===== */

    /**
     * 統計某個藝術家專輯數量
     */
    @Test
    public void combineTest() {
        long braden = numberOfAlbums(albums.stream(), "bradenlei");
        System.out.println(braden);
    }
    public long numberOfAlbums(Stream<Album> albums, String artistName) {
        return albums.map(album -> album.getMusicians())
                .filter(musicians -> musicians.filter(musician -> artistName.equals(musician.getName())).count() > 0)
                .count();
    }

    /**
     * 計算每個藝術家的專輯數(counting)
     */
    @Test
    public void combineTest2() {
        Map<Artist, Long> count = count(albums.stream());
        System.out.println(count.get(artist1)); // out -> 2
    }
    public Map<Artist, Long> count(Stream<Album> albums) {
        return albums.collect(groupingBy(album -> album.getMainMusician(), counting()));
    }

    /**
     * 收集每個藝術家的專輯名稱 (mapping)
     */
    @Test
    public void combineTest3() {
        Map<Artist, List<String>> artistListMap = collectName(albums.stream());
        System.out.println(artistListMap.get(artist1)); // out -> [專輯1, 專輯2]
    }
    public Map<Artist, List<String>> collectName(Stream<Album> alubms) {
        return alubms.collect(groupingBy(Album::getMainMusician, mapping(Album::getName, toList())));
    }

    /** =====重構和定製收集器===== */

    /**
     * 使用除joining之外的方法拼接字串
     *
     * 獲取藝術家姓名 (初步)
     */
    @Test
    public void jointTest() {
        StringBuilder reduced = artists.stream()
                .map(Artist::getName)
                .reduce(new StringBuilder(), (builder, name) -> {
                    if (builder.length() > 0) builder.append(", ");

                    return builder.append(name);
                }, (left, right) -> left.append(right));
        reduced.insert(0, "[");
        reduced.append("]");
        String result = reduced.toString();

        System.out.println(result); // out -> [ace, each]
    }
    /**
     * 優化(自定義StringCombiner)
     */
    @Test
    public void jointTest2() {
        StringCombiner reduced = artists.stream()
                .map(Artist::getName)
                .reduce(new StringCombiner(", ", "[", "]"),
                        StringCombiner::add,
                        StringCombiner::merge); // 這裡實際沒有呼叫merge
        String string = reduced.toString();
        System.out.println(string);  // out -> [ace, each]
    }

    /***
     * 進一步優化(使用定製收集器StringCollector收集)
     */
     @Test
    public void jointTest3() {
         String collect = artists.stream()
                 .map(Artist::getName)
                 .collect(new StringCollector(", ", "[", "]"));
         System.out.println(collect); // out -> [ace, each]
     }

    /**
     * 用reducing代替自定義收集器
     */
    @Test
    public void jointtTest4() {
        // 這種方法的缺點是第二個引數每次都需要new一個物件
        StringCombiner collect = artists.stream()
                .map(Artist::getName)
                .collect(Collectors.reducing(
                        new StringCombiner(", ", "[", "]"),
                        name -> new StringCombiner(", ", "[", "]").add(name),
                        StringCombiner::merge));  // 這裡會呼叫merge, 每次都是兩個StringCombiner物件合併, 所以修改StringCombiner.add即可得到正確格式[ace, each]
        String s = collect.toString();
        System.out.println(s);  // out -> [ace[each]
    }
}