最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

java8列表中通過stream流根據(jù)對象屬性去重的三種方式

 更新時間:2024年08月21日 11:55:53   作者:我認(rèn)不到你  
這篇文章主要介紹了java8列表中通過stream流根據(jù)對象屬性去重的三種方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

一、簡單去重

public class DistinctTest {
    /**
     * 沒有重寫 equals 方法
     */
    @Setter
    @Getter
    @ToString
    @AllArgsConstructor
    @NoArgsConstructor
    public static class User {
        private String name;
        private Integer age;
    }

    /**
     * lombok(@Data) 重寫了 equals 方法 和 hashCode 方法
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class User2 {
        private String name;
        private Integer age;
    }

    @Test
    public void easyTest() {
        List<Integer> integers = Arrays.asList(1, 1, 2, 3, 4, 4, 5, 6, 77, 77);
        System.out.println("======== 數(shù)字去重 =========");
        System.out.print("原數(shù)字列表:");
        integers.forEach(x -> System.out.print(x + " "));
        System.out.println();
        System.out.print("去重后數(shù)字列表:");
        integers.stream().distinct().collect(Collectors.toList()).forEach(x -> System.out.print(x + " "));

        System.out.println();
        System.out.println();

        List<User> list = Lists.newArrayList();
        User three = new User("張三", 18);
        User three2 = new User("張三", 18);
        User three3 = new User("張三", 24);
        User four = new User("李四", 18);
        list.add(three);
        list.add(three);
        list.add(three2);
        list.add(three3);
        list.add(four);
        System.out.println("======== 沒有重寫equals方法的話,只能對相同對象(如:three)進(jìn)行去重,不能做到元素相同就可以去重) =========");
        // 沒有重寫 equals 方法時,使用的是超類 Object 的 equals 方法
        // 等價(jià)于兩個對象 == 的比較,只能篩選同一個對象
        System.out.println("初始對象列表:");
        list.forEach(System.out::println);
        System.out.println("簡單去重后初始對象列表:");
        list.stream().distinct().collect(Collectors.toList()).forEach(System.out::println);

        System.out.println();
        System.out.println();

        List<User2> list2 = Lists.newArrayList();
        User2 five = new User2("王五", 18);
        User2 five2 = new User2("王五", 18);
        User2 five3 = new User2("王五", 24);
        User2 two = new User2("二蛋", 18);
        list2.add(five);
        list2.add(five);
        list2.add(five2);
        list2.add(five3);
        list2.add(two);
        System.out.println("======== 重寫了equals方法的話,可以做到元素相同就可以去重) =========");
        // 所以如果只需要寫好 equals 方法 和 hashCode 方法 也能做到指定屬性的去重
        System.out.println("初始對象列表:");
        list2.forEach(System.out::println);
        System.out.println("簡單去重后初始對象列表:");
        list2.stream().distinct().collect(Collectors.toList()).forEach(System.out::println);
    }
}

二、根據(jù)對象某個屬性去重

0、User對象

    /**
     * 沒有重寫 equals 方法
     */
    @Setter
    @Getter
    @ToString
    @AllArgsConstructor
    @NoArgsConstructor
    public static class User {
        private String name;
        private Integer age;
    }

1、使用filter進(jìn)行去重

    @Test
    public void objectTest() {
        List<User> list = Arrays.asList(
                new User(null, 18),
                new User("張三", null),
                null,
                new User("張三", 24),
                new User("張三5", 24),
                new User("李四", 18)
        );
        System.out.println("初始對象列表:");
        list.forEach(System.out::println);
        System.out.println();
        System.out.println("======== 使用 filter ,根據(jù)特定屬性進(jìn)行過濾(重不重寫equals方法都不重要) =========");
        System.out.println("根據(jù)名字過濾后的對象列表:");
        // 第一個 filter 是用于過濾 第二個 filter 是用于去重
        List<User> collect = list.stream().filter(o -> o != null && o.getName() != null)
                .filter(distinctPredicate(User::getName)).collect(Collectors.toList());
        collect.forEach(System.out::println);
        System.out.println("根據(jù)年齡過濾后的對象列表:");
        List<User> collect1 = list.stream().filter(o -> o != null && o.getAge() != null)
                .filter(distinctPredicate(User::getAge)).collect(Collectors.toList());
        collect1.forEach(System.out::println);
    }

    /**
     * 列表對象去重
     */
    public <K, T> Predicate<K> distinctPredicate(Function<K, T> function) {
        // 因?yàn)閟tream流是多線程操作所以需要使用線程安全的ConcurrentHashMap
        ConcurrentHashMap<T, Boolean> map = new ConcurrentHashMap<>();
        return t -> null == map.putIfAbsent(function.apply(t), true);
    }

測試

①、疑惑

  • 既然 filter 里面調(diào)用的是 distinctPredicate 方法,而該方法每次都 new 一個新的 map 對象,那么 map 就是新的,怎么能做到可以過濾呢

②、解惑

  • 先看一下 filter 的部分實(shí)現(xiàn)邏輯,他使用了函數(shù)式接口 Predicate ,每次調(diào)用filter時,會使用 predicate 對象的 test 方法,這個對象的test 方法就是 null == map.putIfAbsent(function.apply(t), true)
  • 而 distinctPredicate 方法作用就是生成了一個線程安全的 Map 集合,和一個 predicate 對象,且該對象的 test 方法為 null == map.putIfAbsent(function.apply(t), true)
  • 之后 stream 流的 filter 方法每次都只會使用 predicate 對象的 test 方法,而該 test 方法中的 map 對象在該流中是唯一的,并不會重新初始化
    @Override
    public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) {
        Objects.requireNonNull(predicate);
        return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE,
                                     StreamOpFlag.NOT_SIZED) {
            @Override
            Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) {
                return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {
                    @Override
                    public void begin(long size) {
                        downstream.begin(-1);
                    }

                    @Override
                    public void accept(P_OUT u) {
                        if (predicate.test(u))
                            downstream.accept(u);
                    }
                };
            }
        };
    }

2、使用Collectors.toMap() 實(shí)現(xiàn)根據(jù)某一屬性去重(這個可以實(shí)現(xiàn)保留前一個還是后一個)

要注意 Collectors.toMap(key,value) 中 value 不能為空,會報(bào)錯,key 可以為 null,但會被轉(zhuǎn)換為字符串的 “null”

    @Test
    public void objectTest() {
        List<User> list = Arrays.asList(
                new User(null, 18),
                new User("張三", null),
                null,
                new User("張三", 24),
                new User("張三5", 24),
                new User("李四", 18)
        );

        System.out.println("初始對象列表:");
        list.forEach(System.out::println);
        System.out.println();
        System.out.println("======== 使用 Collectors.toMap() 實(shí)現(xiàn)根據(jù)某一屬性去重 =========");
        System.out.println("根據(jù)名字過濾后的對象列表 寫法1:");
        // (v1, v2) -> v1 的意思 兩個名字一樣的話(key一樣),存前一個 value 值
        Map<String, User> collect = list.stream().filter(Objects::nonNull).collect(Collectors.toMap(User::getName, o -> o, (v1, v2) -> v1));
        // o -> o 也可以寫為 Function.identity() ,兩個是一樣的,但后者可能比較優(yōu)雅,但閱讀性不高,如下
        // Map<String, User> collect = list.stream().filter(Objects::nonNull).collect(Collectors.toMap(User::getName, Function.identity(), (v1, v2) -> v1));
        List<User> list2 = new ArrayList<>(collect.values());
        list2.forEach(System.out::println);
        System.out.println("根據(jù)名字過濾后的對象列表 寫法2:");
        Map<String, User> map2 = list.stream().filter(o -> o != null && o.getName() != null)
                .collect(HashMap::new, (m, o) -> m.put(o.getName(), o), HashMap::putAll);
        list2 = new ArrayList<>(map2.values());
        list2.forEach(System.out::println);
        
        System.out.println("根據(jù)年齡過濾后的對象列表:");
        // (v1, k2) -> v2 的意思 兩個年齡一樣的話(key一樣),存后一個 value 值
        Map<Integer, User> collect2 = list.stream().filter(Objects::nonNull).collect(Collectors.toMap(User::getAge, o -> o, (v1, v2) -> v2));
        list2 = new ArrayList<>(collect2.values());
        list2.forEach(System.out::println);

    }

測試

3、Collectors.toMap() 的變種 使用 Collectors.collectingAndThen()

Collectors.collectingAndThen() 函數(shù) 它可接受兩個參數(shù),第一個參數(shù)用于 reduce操作,而第二參數(shù)用于 map操作。

也就是,先把流中的所有元素傳遞給第一個參數(shù),然后把生成的集合傳遞給第二個參數(shù)來處理。

    @Test
    public void objectTest() {
        List<User> list = Arrays.asList(
                new User(null, 18),
                new User("張三", null),
                null,
                new User("張三", 24),
                new User("張三5", 24),
                new User("李四", 18)
        );
        System.out.println("初始對象列表:");
        list.forEach(System.out::println);
        System.out.println();
        System.out.println("======== 使用 Collectors.toMap() 實(shí)現(xiàn)根據(jù)某一屬性去重 =========");
        System.out.println("根據(jù)名字過濾后的對象列表:");
        ArrayList<User> collect1 = list.stream().filter(o -> o != null && o.getName() != null).collect(
                Collectors.collectingAndThen(Collectors.toMap(User::getName, o -> o, (k1, k2) -> k2), x-> new ArrayList<>(x.values())));
        collect1.forEach(System.out::println);
        System.out.println("======== 或者 ==========");
        List<User> collect = list.stream().filter(o -> o != null && o.getName() != null).collect(
                Collectors.collectingAndThen(Collectors.toCollection(
                        () -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList<User>::new));
        collect.forEach(System.out::println);
    }

測試

三、測試哪個方法比較快

    @Test
    public void objectTest() {
        List<User> list = new ArrayList<>(Arrays.asList(
                new User(null, 18),
                new User("張三", null),
                null,
                new User("張三", 24),
                new User("張三5", 24),
                new User("李四", 18)
        ));
        for (int i = 0; i < 100000; i++) {
            list.add(new User((Math.random() * 10) + "", (int) (Math.random() * 10)));
        }
        System.out.println("======== 測試速度 =========");
        long startTime = System.currentTimeMillis();
        List<User> list1 = list.stream().filter(o -> o != null && o.getName() != null)
                .filter(distinctPredicate(User::getName)).collect(Collectors.toList());
        long endTime = System.currentTimeMillis();
        System.out.println("filter 用時 :" + (endTime - startTime));

        System.out.println();
        startTime = System.currentTimeMillis();
        Map<String, User> map1 = list.stream().filter(o -> o != null && o.getName() != null)
                .collect(Collectors.toMap(User::getName, o -> o, (v1, v2) -> v1));
        List<User> list2 = new ArrayList<>(map1.values());
        endTime = System.currentTimeMillis();
        System.out.println("map1 用時 :" + (endTime - startTime));

        System.out.println();
        startTime = System.currentTimeMillis();
        ArrayList<User> list3 = list.stream().filter(o -> o != null && o.getName() != null).collect(
                Collectors.collectingAndThen(Collectors.toMap(User::getName, o -> o, (k1, k2) -> k2), x -> new ArrayList<>(x.values())));
        endTime = System.currentTimeMillis();
        System.out.println("map2 用時 :" + (endTime - startTime));

        System.out.println();
        startTime = System.currentTimeMillis();
        List<User> list4 = list.stream().filter(o -> o != null && o.getName() != null).collect(
                Collectors.collectingAndThen(Collectors.toCollection(
                        () -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList<User>::new));
        endTime = System.currentTimeMillis();
        System.out.println("map3 用時 :" + (endTime - startTime));

        System.out.println();
        startTime = System.currentTimeMillis();
        Map<String, User> map2 = list.stream().filter(o -> o != null && o.getName() != null)
                .collect(HashMap::new, (m, o) -> m.put(o.getName(), o), HashMap::putAll);
        List<User> list5 = new ArrayList<>(map2.values());
        endTime = System.currentTimeMillis();
        System.out.println("map4 用時 :" + (endTime - startTime));
    }

測試:

總結(jié)

1、去重最快

	ArrayList<User> list3 = list.stream().filter(o -> o != null && o.getName() != null).collect(
                Collectors.collectingAndThen(Collectors.toMap(User::getName, o -> o, (k1, k2) -> k2), x -> new ArrayList<>(x.values())));
	// 或者
	Map<String, User> map2 = list.stream().filter(o -> o != null && o.getName() != null)
                .collect(HashMap::new, (m, o) -> m.put(o.getName(), o), HashMap::putAll);
	List<User> list5 = new ArrayList<>(map2.values());

2、其次

        Map<String, User> map1 = list.stream().filter(o -> o != null && o.getName() != null)
                .collect(Collectors.toMap(User::getName, o -> o, (v1, v2) -> v1));
        List<User> list2 = new ArrayList<>(map1.values());

		// distinctPredicate 是一個方法 本文中有 ,可以 ctrl + f 查找
        List<User> list1 = list.stream().filter(o -> o != null && o.getName() != null)
                .filter(distinctPredicate(User::getName)).collect(Collectors.toList());

3、最慢

	List<User> list4 = list.stream().filter(o -> o != null && o.getName() != null).collect(
                Collectors.collectingAndThen(Collectors.toCollection(
                        () -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList<User>::new));

以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java實(shí)現(xiàn)在線編輯預(yù)覽office文檔詳解

    Java實(shí)現(xiàn)在線編輯預(yù)覽office文檔詳解

    PageOffice是一款在線的office編輯軟件,幫助Web應(yīng)用系統(tǒng)或Web網(wǎng)站實(shí)現(xiàn)用戶在線編輯Word、Excel、PowerPoint文檔,下面我們就來看看如何使用Java實(shí)現(xiàn)在線預(yù)覽office吧
    2024-01-01
  • idea如何自動添加版權(quán)許可證信息

    idea如何自動添加版權(quán)許可證信息

    這篇文章主要介紹了idea如何自動添加版權(quán)許可證信息問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • Springboot設(shè)置windows和Linux臨時上傳路徑方式

    Springboot設(shè)置windows和Linux臨時上傳路徑方式

    文章介紹了在Windows和Linux系統(tǒng)中設(shè)置臨時上傳路徑的方法,對于Windows系統(tǒng),可以通過修改注冊表或者環(huán)境變量來實(shí)現(xiàn),而對于Windows和Linux,可以在應(yīng)用程序中注入Bean來設(shè)置臨時上傳路徑
    2026-03-03
  • 徹底卸載、更換jdk全過程

    徹底卸載、更換jdk全過程

    本文介紹了卸載舊版JDK并安裝新版JDK的詳細(xì)步驟,包括卸載JDK、刪除注冊表中的JavaSoft文件夾、刪除環(huán)境變量、刪除exe文件以及安裝新版JDK
    2026-02-02
  • Spring?Boot分離配置文件的多種方式總結(jié)

    Spring?Boot分離配置文件的多種方式總結(jié)

    Spring Boot可以外部化程序配置,以便可以在不同環(huán)境中使用相同的應(yīng)用程序代碼;當(dāng)然Spring Boot可以將配置文件進(jìn)行拆分,以便于激活不同的運(yùn)行環(huán)境,下面這篇文章主要給大家總結(jié)介紹了關(guān)于Spring?Boot分離配置文件的多種方式,需要的朋友可以參考下
    2022-11-11
  • Spring實(shí)現(xiàn)定時任務(wù)的幾種方式總結(jié)

    Spring實(shí)現(xiàn)定時任務(wù)的幾種方式總結(jié)

    Spring Task 是 Spring 框架提供的一種任務(wù)調(diào)度和異步處理的解決方案,可以按照約定的時間自動執(zhí)行某個代碼邏輯它可以幫助開發(fā)者在 Spring 應(yīng)用中輕松地實(shí)現(xiàn)定時任務(wù)、異步任務(wù)等功能,提高應(yīng)用的效率和可維護(hù)性,需要的朋友可以參考下本文
    2024-07-07
  • Java中Calendar類用法實(shí)例詳解

    Java中Calendar類用法實(shí)例詳解

    這篇文章主要給大家介紹了關(guān)于Java中Calendar類用法的相關(guān)資料,Calendar類是Java.util包中提供的一個抽象類,該類從JDK1.1開始出現(xiàn),作為Date類的替代方案,Calendar類中包含了對不同國家地區(qū)日歷的處理,需要的朋友可以參考下
    2023-09-09
  • java 多態(tài)與抽象類詳解總結(jié)

    java 多態(tài)與抽象類詳解總結(jié)

    在面向?qū)ο蟮母拍钪校械膶ο蠖际峭ㄟ^類來描繪的,但是反過來,并不是所有的類都是用來描繪對象的,如果一個類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類就是抽象類,而多態(tài)是同一個行為具有多個不同表現(xiàn)形式或形態(tài)的能力
    2021-11-11
  • Java中的弗洛伊德(Floyd)算法

    Java中的弗洛伊德(Floyd)算法

    這篇文章主要介紹了Java中的弗洛伊德(Floyd)算法,Floyd算法又稱為插點(diǎn)法,是一種利用動態(tài)規(guī)劃的思想尋找給定的加權(quán)圖中多源點(diǎn)之間最短路徑的算法,與Dijkstra算法類似,需要的朋友可以參考下
    2024-01-01
  • Java 并發(fā)編程中如何創(chuàng)建線程

    Java 并發(fā)編程中如何創(chuàng)建線程

    這篇文章主要介紹了Java 并發(fā)編程中如何創(chuàng)建線程,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下
    2021-03-03

最新評論

镇赉县| 中西区| 临朐县| 大埔区| 库伦旗| 深州市| 阳原县| 拜城县| 涞水县| 杭锦旗| 吴堡县| 湛江市| 兴城市| 和田市| 尉氏县| 汪清县| 孝义市| 乡城县| 九江市| 新竹县| 兴仁县| 安吉县| 姚安县| 寿宁县| 尼勒克县| 大邑县| 厦门市| 大兴区| 剑川县| 安义县| 隆子县| 铜鼓县| 崇左市| 伊春市| 隆安县| 格尔木市| 崇阳县| 抚宁县| 正阳县| 昌宁县| 图们市|