java8列表中通過stream流根據(jù)對象屬性去重的三種方式
一、簡單去重
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)進行去重,不能做到元素相同就可以去重) =========");
// 沒有重寫 equals 方法時,使用的是超類 Object 的 equals 方法
// 等價于兩個對象 == 的比較,只能篩選同一個對象
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進行去重
@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ù)特定屬性進行過濾(重不重寫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) {
// 因為stream流是多線程操作所以需要使用線程安全的ConcurrentHashMap
ConcurrentHashMap<T, Boolean> map = new ConcurrentHashMap<>();
return t -> null == map.putIfAbsent(function.apply(t), true);
}測試

①、疑惑
- 既然 filter 里面調用的是 distinctPredicate 方法,而該方法每次都 new 一個新的 map 對象,那么 map 就是新的,怎么能做到可以過濾呢
②、解惑
- 先看一下 filter 的部分實現(xiàn)邏輯,他使用了函數(shù)式接口 Predicate ,每次調用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() 實現(xiàn)根據(jù)某一屬性去重(這個可以實現(xiàn)保留前一個還是后一個)
要注意 Collectors.toMap(key,value) 中 value 不能為空,會報錯,key 可以為 null,但會被轉換為字符串的 “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() 實現(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() 實現(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));
}測試:

總結
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)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Java springboot Mongodb增刪改查代碼實例
這篇文章主要介紹了Java springboot Mongodb增刪改查代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-07-07
Mybatis-Plus通過SQL注入器實現(xiàn)批量插入的實踐
本文主要介紹了Mybatis-Plus通過SQL注入器實現(xiàn)批量插入的實踐,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-08-08
Java中的線程同步與ThreadLocal無鎖化線程封閉實現(xiàn)
這篇文章主要介紹了Java中的線程同步與ThreadLocal無鎖化線程封閉實現(xiàn),Synchronized關鍵字與ThreadLocal變量的使用是Java中線程控制的基礎,需要的朋友可以參考下2016-03-03
SpringBoot使用@Validated處理校驗的方法步驟
@Validated?注解的主要目的是啟用和利用?Spring?的驗證框架,它可以用于類上也可以用于方法參數(shù)上,本文給大家介紹了SpringBoot使用@Validated優(yōu)雅的處理校驗的方法步驟,通過代碼示例介紹的非常詳細,需要的朋友可以參考下2024-08-08
idea快速實現(xiàn)將SpringBoot項目打包Docker鏡像并部署
本文主要介紹了idea快速實現(xiàn)將SpringBoot項目打包Docker鏡像并部署,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-04-04
解決springboot項目啟動失敗Could not initialize class&
這篇文章主要介紹了解決springboot項目啟動失敗Could not initialize class com.fasterxml.jackson.databind.ObjectMapper問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06

