1. 程式人生 > >java8流計算去重方法進階版(優質文章)

java8流計算去重方法進階版(優質文章)

這裡一共介紹3種方式排序

1、Stream提供的distinct()方法只能去除重複的物件,無法根據指定的物件屬性進行去重,可以應付簡單場景。

2、

List<Book> unique = books.stream().collect(
        collectingAndThen(toCollection(() -> new TreeSet<>(Comparator.comparing(o -> o.getId()))),
            ArrayList::new));

使用上述程式碼可以根據指定元素去重(book的id屬性),原理是把id作為key放入set中,然後在轉換為list。這種方式能夠去重,但不優雅,因為需要執行流的終端操作,把流轉換為List。這樣的話,流就不能繼續使用了,不優雅,不好。但網上大多是這種方法。所以第三種方法才是我們要推薦的。

3、

unique = books.stream()
        .filter(distinctByKey(o -> o.getId()))
        .collect(Collectors.toList());

這種方法是不是看著優雅多了?非常的java8了?是的。但這個distinctByKey()這個方法是自定義的。定義如下:

public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
    Map<Object, Boolean> seen = new ConcurrentHashMap<>();
    return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
  }

非常感謝《Solr權威指南》的作者,蘭小偉提供的程式碼和思路。這種方法要比網上絕大多數的方法好。

我來稍微描述一下這個方法,便於新手理解。

首先是filter方法,返回一個流,需要一個Predicate型別的引數(多嘴介紹下Predicate這個函式式介面,接受一個引數,返回布林值)。

Stream<T> filter(Predicate<? super T> predicate)
filter根據Predicate返回的布林值來判斷是否要過濾掉,會過濾掉返回值為false的資料。而我們自己定義的distinctByKey返回值就是Predicate,所以可以作為引數傳入filter。distinctByKey也需要一個Function的引數。distinctByKey先是定義了一個執行緒安全的Map(相比於Hashtable以及Collections.synchronizedMap(),ConcurrentHashMap線上程安全的基礎上提供了更好的寫併發能力,但同時降低了對讀一致性的要求
),因為在流計算中是多執行緒處理的,需要執行緒安全。然後將值作為key,TRUE作為value put到map中。這裡的put方法使用的是putIfAbsent()。putIfAbsent()方法是如果key不存在則put如map中,並返回null。若key存在,則直接返回key所對應的value值。文章末尾貼上putIfAbsent()原始碼。所以 
seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;

這裡用 == null判斷是否存在於map了,若存在則返回false,不存在則為true。以用來達到去重的目的。

default V putIfAbsent(K key, V value) {
        V v = get(key);
        if (v == null) {
            v = put(key, value);
        }

        return v;
    }
注:putIfAbsent()定義在Map介面中,是預設方法。