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


免責聲明!

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