• List列表运用Java8的stream流按某字段去重


    问题

    项目中经常会遇到列表去重的问题,一般可使用Java8的stream()流提供的distinct()方法:list.stream().distinct()
    list的类型为List<String>List<Integer>,list里的元素为简单包装类型。
    或者List<Xxx>,其中Xxx为自定义对象类型,重写equalshashCode方法,可根据业务情况来实现,如id相同即认为对象相等。
    有时会遇到这种情况,需要对按对象里的某字段来去重。

    例如:

    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    class Book {
    
        public static Book of(Long id, String name, String createTime) {
            return new Book(id, name, Date.from(LocalDateTime.parse(createTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")).atZone(ZoneId.systemDefault()).toInstant()));
        }
    
        private Long id;
    
        private String name;
    
        private Date createTime;
    }
    

    现在我们要按name字段来去重,假设list如下:

    List<Book> books = new ArrayList<>();
    books.add(Book.of(1L, "Thinking in Java", "2021-06-29 17:13:14"));
    books.add(Book.of(2L, "Hibernate in action", "2021-06-29 18:13:14"));
    books.add(Book.of(3L, "Thinking in Java", "2021-06-29 19:13:14"));
    

    思路

    1. 重写Book类的equalshashCode方法,以name来判断比较是否相同,然后用stream的distinct方法来去重

    代码:

    class Book {
        ...
    
        @Override
        public String toString() {
            return String.format("(%s,%s,%s)", id, name, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(createTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()));
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Book book = (Book) o;
            return Objects.equals(name, book.name);
        }
    }
    
    List<Book> distinctNameBooks1 = books.stream().distinct().collect(Collectors.toList());
    System.out.println(distinctNameBooks1);
    

    总结:
    通过重写equalshashCode方法,按实际需求来比较,可直接使用stream的distinct方法去重,比较方便;
    有时对象类不方便或者不能修改,如它已实现好或者是引用的三方包不能修改,该方法不能灵活地按字段来去重。

    1. 通过Collectors.collectingAndThenCollectors.toCollection,里面用TreeSet在构造函数中指定字段

    代码:

    List<Book> distinctNameBooks2 = books.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(o -> o.getName()))), ArrayList::new));
    System.out.println(distinctNameBooks2);
    

    总结:
    使用stream流提供的方法,代码很简洁,但不足是虽然实现了去重效果,但list里的顺序变化了,而有的场景需要保持顺序。

    1. 通过stream的filter方法来去重,定义一个去重方法,参数为Function类型,返回值为Predicate类型

    代码:

    public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
        Map<Object, Boolean> map = new HashMap<>();
        return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }
    
    List<Book> distinctNameBooks3 = books.stream().filter(distinctByKey(o -> o.getName())).collect(Collectors.toList());
    System.out.println(distinctNameBooks3);
    

    总结:
    通过封装定义一个去重方法,配合filter方法可灵活的按字段去重,保持了原列表的顺序,不足之处是内部定义了一个HashMap,有一定内存占用,并且多了一个方法定义。

    1. 通过stream的filter方法来去重,不定义去重方法,在外面创建HashMap

    代码:

    Map<Object, Boolean> map = new HashMap<>();
    List<Book> distinctNameBooks4 = books.stream().filter(i -> map.putIfAbsent(i.getName(), Boolean.TRUE) == null).collect(Collectors.toList());
    System.out.println(distinctNameBooks4);
    

    总结:
    仍然是配合filter方法实现去重,没有单独创建方法,临时定义一个HashMap,保持了原列表的顺序,不足之处是有一定内存占用。

    PS:暂时没找到stream流原生支持的可按某字段去重并且保持原列表顺序的方法

    参考

    java steam List指定字段去重 https://www.cnblogs.com/vae860514/p/10832414.html
    Java1.8—使用Stream通过对象某个字段对集合进行去重 https://blog.csdn.net/qq_28163175/article/details/103297656

  • 相关阅读:
    linux解压缩各种命令
    memset
    STRUCT_OFFSET( s, m )宏
    请问这个宏是什么意思 #define NOTUSED(v) ((void) v)?
    typedef特殊用法:typedef void* (*fun)(void*)
    localtime、localtime_s、localtime_r的使用
    Git使用(一)
    【转】linux gcc _attribute__((weak)) 简介及作用
    我的技术博客
    .net 4.0(2.0)“检测到有潜在危险的 Request.Form 值”的解决方法
  • 原文地址:https://www.cnblogs.com/cdfive2018/p/15064524.html
Copyright © 2020-2023  润新知