一、distinct去重
1、distinct簡單去重
Stream提供的distinct()方法除了去除重復的對象
private static void testDistinct() { int[] ints = {1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4}; Arrays.stream(ints).distinct().forEach(number -> { System.out.println("number ->" + number); }); }
結果如下:
2、distinct根據指定的對象屬性進行去重
注意:必須重寫hashcode和equals方法
實體類
@Data @AllArgsConstructor public class UserInfo { private Integer Id; private String name; private Integer age; @Override public boolean equals(Object obj) { if (obj instanceof UserInfo) { UserInfo tmp = (UserInfo) obj; if (this.getName().equals(tmp.getName())) { return true; } } return false; } @Override public int hashCode() { return Objects.hash(name); } }
測試類
@SpringBootTest public class distinctTest { @Test public void test() { List<UserInfo> list = new ArrayList(); list.add(new UserInfo(1,"小明",1)); list.add(new UserInfo(2,"小s",2)); list.add(new UserInfo(1,"小明",2)); list.add(new UserInfo(3,"小明",3)); List<UserInfo> collect = list.stream().distinct().collect(Collectors.toList()); System.out.println(collect); } }
結果:
[UserInfo(Id=1, name=小明, age=1), UserInfo(Id=2, name=小s, age=2)]
有兩種方式根據對象的屬性進行去重,如下:
二、將對象的屬性放入set再轉為List(不建議)
List<Book> unique = books.stream().collect(collectingAndThen(toCollection(() -> new TreeSet<>(Comparator.comparing(o -> o.getId()))), ArrayList::new));
使用上述代碼可以根據指定元素去重(book的id屬性),原理是把id作為key放入set中,然后在轉換為list。這種方式能夠去重,但不優雅,因為需要執行流的終端操作,把流轉換為List。這樣的話,流就不能繼續使用了,不優雅,不好。但網上大多是這種方法。所以第三種方法才是我們要推薦的。
三、使用自定義的distinctByKey方法去重(建議)
unique = books.stream().filter(distinctByKey(o -> o.getId())).collect(Collectors.toList());
這種方法是不是看着優雅多了?非常的java8了?是的。但這個distinctByKey()這個方法是自定義的。定義如下:
private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) { Map<Object, Boolean> seen = new ConcurrentHashMap<>(); return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; }
首先是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方法如下:
default V putIfAbsent(K key, V value) { V v = get(key); if (v == null) { v = put(key, value); } return v; }
所以
seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
這里用 == null判斷是否存在於map了,若存在則返回false,不存在則為true。以用來達到去重的目的。
注:putIfAbsent()定義在Map接口中,是默認方法。
項目代碼:
List<MaterialsTemplate> intelDrugStoreTemplateList = ExcelImportUtil.importExcel(file.getInputStream(), MaterialsTemplate.class, params); List<MaterialsTemplate> distinct = intelDrugStoreTemplateList.stream().filter(distinctByKey(MaterialsTemplate::getMmNo)).collect(Collectors.toList());
案例:
實體類
@Data @AllArgsConstructor public class UserInfo { private Integer Id; private String name; private Integer age; }
測試類
@SpringBootTest public class distinctTest { @Test public void test() { List<UserInfo> list = new ArrayList(); list.add(new UserInfo(1,"小明",1)); list.add(new UserInfo(2,"小s",2)); list.add(new UserInfo(1,"小明",2)); list.add(new UserInfo(3,"小明",3)); List<UserInfo> collect = list.stream().filter(distinctByKey(UserInfo::getName)).collect(Collectors.toList()); System.out.println(collect); } private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) { Map<Object, Boolean> seen = new ConcurrentHashMap<>(); return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; } }
結果:
[UserInfo(Id=1, name=小明, age=1), UserInfo(Id=2, name=小s, age=2)]
四、Collectors.toMap方法去重
該方法只適合轉成map后對key去重,在之前的distinct和filter中默認重復的數據去重只能保留之前的,沒法做到后面重復的數據覆蓋前面的,那Collectors.toMap方法就可以隨意定義這個操作動作。
實體類
@Data @AllArgsConstructor public class UserInfo { private Integer Id; private String name; private Integer age; }
測試類
@SpringBootTest public class distinctTest { @Test public void test() { List<UserInfo> list = new ArrayList(); list.add(new UserInfo(1,"小明",1)); list.add(new UserInfo(2,"小s",2)); list.add(new UserInfo(1,"小明",2)); list.add(new UserInfo(3,"小明",3)); Map<Integer, UserInfo> map4 = list.stream().collect(Collectors.toMap(UserInfo::getId, item -> item,(key1,key2) -> key2)); System.out.println(map4); } }
結果:
{1=UserInfo(Id=1, name=小明, age=2), 2=UserInfo(Id=2, name=小s, age=2), 3=UserInfo(Id=3, name=小明, age=3)}
其中(key1,key2) -> key1)就是定義如果出現重復,以前面key為准,如果要以后面數據為准,也就是讓重復的數據用后面的覆蓋,那么就可以寫成:(key1,key2) -> key2)。
@SpringBootTest public class distinctTest { @Test public void test() { List<UserInfo> list = new ArrayList(); list.add(new UserInfo(1,"小明",1)); list.add(new UserInfo(2,"小s",2)); list.add(new UserInfo(1,"小明",2)); list.add(new UserInfo(3,"小明",3)); Map<Integer, UserInfo> map4 = list.stream().collect(Collectors.toMap(UserInfo::getId, item -> item,(key1,key2) -> key1)); System.out.println(map4); } }
結果:
{1=UserInfo(Id=1, name=小明, age=1), 2=UserInfo(Id=2, name=小s, age=2), 3=UserInfo(Id=3, name=小明, age=3)}
這種去重的方式好處是不需要重寫equals方法,也不需要定義工具類,隨時可以用,但僅限於轉Map的場景。