請直接移步原文Java中List集合對象去重及按屬性去重的8種方法
只記錄自己喜歡的幾種方法
- 對象元素整體去重的2種方法
- 按照對象屬性去重的4種方法
預備數據
public class ListRmDuplicate {private List<String> list;private List<Player> playerList;@BeforeEachpublic void setup() {list = new ArrayList<>();list.add("kobe");list.add("james");list.add("curry");list.add("zimug");list.add("zimug");playerList= new ArrayList<>();playerList.add(new Player("kobe","10000")); //科比萬歲playerList.add(new Player("james","32"));playerList.add(new Player("curry","30"));playerList.add(new Player("zimug","27")); // 注意這里名字重復playerList.add(new Player("zimug","18")); //注意這里名字和年齡重復playerList.add(new Player("zimug","18")); //注意這里名字和年齡重復}
}
一、集合元素整體去重
List中的String類型以集合元素對象為單位整體去重。如果你的List放入的是Object對象,需要你去實現對象的equals和hashCode方法,去重的代碼實現方法和List去重是一樣的
方法一
是大家最容易想到的,先把List數據放入Set,因為Set數據結構本身具有去重的功能,所以再將SET轉為List之后就是去重之后的結果。這種方法在去重之后會改變原有的List元素順序,因為HashSet本身是無序的,而TreeSet排序也不是List種元素的原有順序
。
@Test
void testRemove1() {/*Set<String> set = new HashSet<>(list);List<String> newList = new ArrayList<>(set);*///去重并排序的方法(如果是字符串,按字母表排序。如果是對象,按Comparable接口實現排序)//List<String> newList = new ArrayList<>(new TreeSet<>(list));//簡寫的方法List<String> newList = new ArrayList<>(new HashSet<>(list));System.out.println( "去重后的集合: " + newList);
}
方法二
使用就比較簡單,先用stream方法將集合轉換成流,然后distinct去重,最后在將Stream流collect收集為List。
@Test
void testRemove2() {List<String> newList = list.stream().distinct().collect(Collectors.toList());System.out.println( "去重后的集合: " + newList);
}
二、按照集合元素對象屬性去重
其實在實際的工作中,按照集合元素對象整體去重的應用的還比較少,更多的是要求我們按照元素對象的某些屬性進行去重。
看到這里請大家回頭去看一下上文中,構造的初始化數據playerList,特別注意其中的一些重復元素,以及成員變量重復。
方法一
為TreeSet實現Comparator接口,如果我們希望按照Player的name屬性進行排序,就去在Comparator接口中比較name。下文中寫了兩種實現Comparator接口方法:
- lambda表達式:(o1, o2) -> o1.getName().compareTo(o2.getName())
- 方法引用:Comparator.comparing(Player::getName)
@Test
void testRemove5() {//Set<Player> playerSet = new TreeSet<>((o1, o2) -> o1.getName().compareTo(o2.getName()));Set<Player> playerSet = new TreeSet<>(Comparator.comparing(Player::getName));playerSet.addAll(playerList);/*new ArrayList<>(playerSet).forEach(player->{System.out.println(player.toString());});*///將去重之后的結果打印出來new ArrayList<>(playerSet).forEach(System.out::println);
}
輸出結果如下:三個zimug因為name重復,另外兩個被去重。但是因為使用到了TreeSet,list中元素被重新排序。
Player{name=‘curry’, age=‘30’}
Player{name=‘james’, age=‘32’}
Player{name=‘kobe’, age=‘10000’}
Player{name=‘zimug’, age=‘27’}
方法二(stream)
- 首先用stream()把list集合轉換成流
- 然后用collect及toCollection把流轉換成集合
- 然后剩下的就和方法一相同了
@Test
void testRemove6() {List<Player> newList = playerList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Player::getName))),ArrayList::new));newList.forEach(System.out::println);
}
方法三(不會打亂順序)
這種方法也是筆者建議大家使用的一種方法,咋一看好像代碼量更大了,但實際上這種方法是應用比較簡單的方法。
Predicate(有人管這個叫斷言,從英文的角度作為名詞可以翻譯為謂詞,作為動詞可以翻譯為斷言)。謂詞就是用來修飾主語的,比如:喜歡唱歌的小鳥,喜歡唱歌就是謂詞,用來限定主語的范圍。所以我們這里是用來filter過濾的,也是用來限制主語范圍的,所以我認為翻譯為謂詞更合適。隨便吧,看你怎么覺得怎么理解合理、好記,你就怎么來。
- 首先我們定義一個謂詞Predicate用來過濾,過濾的條件是distinctByKey。謂詞返回ture元素保留,返回false元素被過濾掉。
- 當然我們的需求是過濾掉重復元素。我們去重邏輯是通過map的putIfAbsent實現的。putIfAbsent方法添加鍵值對,如果map集合中沒有該key對應的值,則直接添加,并返回null,如果已經存在對應的值,則依舊為原來的值。
- 如果putIfAbsent返回null表示添加數據成功(不重復),如果putIfAbsent返回value(value==null :false),則滿足了distinctByKey謂詞的條件元素被過濾掉。
這種方法雖然看上去代碼量增大了,但是distinctByKey謂詞方法只需要被定義一次,就可以無限復用。
@Test
void testRemove7() {List<Player> newList = new ArrayList<>();playerList.stream().filter(distinctByKey(p -> p.getName())) //filter保留true的值.forEach(newList::add);newList.forEach(System.out::println);
}static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {Map<Object,Boolean> seen = new ConcurrentHashMap<>();//putIfAbsent方法添加鍵值對,如果map集合中沒有該key對應的值,則直接添加,并返回null,如果已經存在對應的值,則依舊為原來的值。//如果返回null表示添加數據成功(不重復),不重復(null==null :TRUE)return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
輸出結果如下:三個zimug因為name重復,另外兩個被去重。并且沒有打亂List的原始順序
Player{name=‘kobe’, age=‘10000’}
Player{name=‘james’, age=‘32’}
Player{name=‘curry’, age=‘30’}
Player{name=‘zimug’, age=‘27’}
方法四
第四種方法實際上不是新方法,上面的例子都是按某一個對象屬性進行去重,如果我們想按照某幾個元素進行去重,就需要對上面的三種方法進行改造。
我只改造其中一個,另外幾個改造的原理是一樣的,就是把多個比較屬性加起來,作為一個String屬性進行比較。
@Test
void testRemove8() {Set<Player> playerSet = new TreeSet<>(Comparator.comparing(o -> (o.getName() + "" + o.getAge())));playerSet.addAll(playerList);new ArrayList<>(playerSet).forEach(System.out::println);
}