一常柄、本文描述
這一篇文章寫一下List集合元素去重的8種方法,實(shí)際上通過靈活的運(yùn)用、排列組合不一定是8種抱婉,可能有18種方法。
- 對(duì)象元素整體去重的4種方法
- 按照對(duì)象屬性去重的4種方法
為了在下文中進(jìn)行測(cè)試內(nèi)容講解桌粉,我們先做一些初始化數(shù)據(jù)
public class ListRmDuplicate {
private List<String> list;
private List<Player> playerList;
@BeforeEach
public 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")); // 注意這里名字重復(fù)
playerList.add(new Player("zimug","18")); //注意這里名字和年齡重復(fù)
playerList.add(new Player("zimug","18")); //注意這里名字和年齡重復(fù)
}
}
Player對(duì)象就是一個(gè)普通的java對(duì)象蒸绩,有兩個(gè)成員變量name與age,實(shí)現(xiàn)了帶參數(shù)構(gòu)造函數(shù)铃肯、toString患亿、equals和hashCode方法、以及GET/SET方法缘薛。
二窍育、集合元素整體去重
下文中四種方法對(duì)List中的String類型以集合元素對(duì)象為單位整體去重卡睦。如果你的List放入的是Object對(duì)象,需要你去實(shí)現(xiàn)對(duì)象的equals和hashCode方法漱抓,去重的代碼實(shí)現(xiàn)方法和List<String>去重是一樣的表锻。
第一種方法
是大家最容易想到的,先把List數(shù)據(jù)放入Set乞娄,因?yàn)镾et數(shù)據(jù)結(jié)構(gòu)本身具有去重的功能瞬逊,所以再將SET轉(zhuǎn)為L(zhǎng)ist之后就是去重之后的結(jié)果。這種方法在去重之后會(huì)改變?cè)械腖ist元素順序仪或,因?yàn)镠ashSet本身是無序的确镊,而TreeSet排序也不是List種元素的原有順序。
@Test
void testRemove1() {
/*Set<String> set = new HashSet<>(list);
List<String> newList = new ArrayList<>(set);*/
//去重并排序的方法(如果是字符串范删,按字母表排序蕾域。如果是對(duì)象,按Comparable接口實(shí)現(xiàn)排序)
//List<String> newList = new ArrayList<>(new TreeSet<>(list));
//簡(jiǎn)寫的方法
List<String> newList = new ArrayList<>(new HashSet<>(list));
System.out.println( "去重后的集合: " + newList);
}
控制臺(tái)打印結(jié)果如下:
去重后的集合: [kobe, james, zimug, curry]
第二種方法
使用就比較簡(jiǎn)單到旦,先用stream方法將集合轉(zhuǎn)換成流旨巷,然后distinct去重,最后在將Stream流collect收集為L(zhǎng)ist添忘。
@Test
void testRemove2() {
List<String> newList = list.stream().distinct().collect(Collectors.toList());
System.out.println( "去重后的集合: " + newList);
}
控制臺(tái)打印結(jié)果如下:
去重后的集合: [kobe, james, curry, zimug]
第三種方法
這種方法利用了set.add(T),如果T元素已經(jīng)存在集合中采呐,就返回false。利用這個(gè)方法進(jìn)行是否重復(fù)的數(shù)據(jù)判斷搁骑,如果不重復(fù)就放入一個(gè)新的newList中斧吐,這個(gè)newList就是最終的去重結(jié)果
//三個(gè)集合類list、newList仲器、set煤率,能夠保證順序
@Test
void testRemove3() {
Set<String> set = new HashSet<>();
List<String> newList = new ArrayList<>();
for (String str :list) {
if(set.add(str)){ //重復(fù)的話返回false
newList.add(str);
}
}
System.out.println( "去重后的集合: " + newList);
}
控制臺(tái)打印結(jié)果和第二種方法一致。
第四種方法
這種方法已經(jīng)脫離了使用Set集合進(jìn)行去重的思維乏冀,而是使用newList.contains(T)方法涕侈,在向新的List添加數(shù)據(jù)的時(shí)候判斷這個(gè)數(shù)據(jù)是否已經(jīng)存在,如果存在就不添加煤辨,從而達(dá)到去重的效果裳涛。
//優(yōu)化 List、newList众辨、set端三,能夠保證順序
@Test
void testRemove4() {
List<String> newList = new ArrayList<>();
for (String cd:list) {
if(!newList.contains(cd)){ //主動(dòng)判斷是否包含重復(fù)元素
newList.add(cd);
}
}
System.out.println( "去重后的集合: " + newList);
}
控制臺(tái)打印結(jié)果和第二種方法一致。
三鹃彻、按照集合元素對(duì)象屬性去重
其實(shí)在實(shí)際的工作中郊闯,按照集合元素對(duì)象整體去重的應(yīng)用的還比較少,更多的是要求我們按照元素對(duì)象的某些屬性進(jìn)行去重。
看到這里請(qǐng)大家回頭去看一下上文中团赁,構(gòu)造的初始化數(shù)據(jù)playerList,特別注意其中的一些重復(fù)元素育拨,以及成員變量重復(fù)。
第一種方法
為TreeSet實(shí)現(xiàn)Comparator接口欢摄,如果我們希望按照Player的name屬性進(jìn)行去重熬丧,就去在Comparator接口中比較name。下文中寫了兩種實(shí)現(xiàn)Comparator接口方法:
- lambda表達(dá)式:(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());
});*/
//將去重之后的結(jié)果打印出來
new ArrayList<>(playerSet).forEach(System.out::println);
}
輸出結(jié)果如下:三個(gè)zimug因?yàn)閚ame重復(fù)怀挠,另外兩個(gè)被去重析蝴。但是因?yàn)槭褂玫搅薚reeSet,list中元素被重新排序绿淋。
Player{name='curry', age='30'}
Player{name='james', age='32'}
Player{name='kobe', age='10000'}
Player{name='zimug', age='27'}
第二種方法
這種方法是網(wǎng)上很多的文章中用來顯示自己很牛的方法闷畸,但是在筆者看來有點(diǎn)脫了褲子放屁,多此一舉吞滞。既然大家都說有這種方法佑菩,我不寫好像我不牛一樣。我為什么說這種方法是“脫了褲子放屁”裁赠?
- 首先用stream()把list集合轉(zhuǎn)換成流
- 然后用collect及toCollection把流轉(zhuǎn)換成集合
- 然后剩下的就和第一種方法一樣了
前兩步不是脫了褲子放屁么倘待?看看就得了,實(shí)際應(yīng)用意義不大组贺,但是如果是為了學(xué)習(xí)Stream流的使用方法,搞出這么一個(gè)例子還是有可取之處的祖娘。
@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);
}
控制臺(tái)打印輸出和第一種方法一樣失尖。
第三種方法
這種方法也是筆者建議大家使用的一種方法,咋一看好像代碼量更大了渐苏,但實(shí)際上這種方法是應(yīng)用比較簡(jiǎn)單的方法掀潮。
Predicate(有人管這個(gè)叫斷言,從英文的角度作為名詞可以翻譯為謂詞琼富,作為動(dòng)詞可以翻譯為斷言)仪吧。謂詞就是用來修飾主語的,比如:喜歡唱歌的小鳥鞠眉,喜歡唱歌就是謂詞薯鼠,用來限定主語的范圍。所以我們這里是用來filter過濾的械蹋,也是用來限制主語范圍的出皇,所以我認(rèn)為翻譯為謂詞更合適。隨便吧哗戈,看你怎么覺得怎么理解合理郊艘、好記,你就怎么來。
- 首先我們定義一個(gè)謂詞Predicate用來過濾纱注,過濾的條件是distinctByKey畏浆。謂詞返回ture元素保留,返回false元素被過濾掉狞贱。
- 當(dāng)然我們的需求是過濾掉重復(fù)元素刻获。我們?nèi)ブ剡壿嬍峭ㄟ^map的putIfAbsent實(shí)現(xiàn)的。putIfAbsent方法添加鍵值對(duì)斥滤,如果map集合中沒有該key對(duì)應(yīng)的值将鸵,則直接添加,并返回null佑颇,如果已經(jīng)存在對(duì)應(yīng)的值顶掉,則依舊為原來的值。
- 如果putIfAbsent返回null表示添加數(shù)據(jù)成功(不重復(fù))挑胸,如果putIfAbsent返回value(value==null :false),則滿足了distinctByKey謂詞的條件元素被過濾掉痒筒。
這種方法雖然看上去代碼量增大了,但是distinctByKey謂詞方法只需要被定義一次茬贵,就可以無限復(fù)用簿透。
@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方法添加鍵值對(duì),如果map集合中沒有該key對(duì)應(yīng)的值解藻,則直接添加老充,并返回null,如果已經(jīng)存在對(duì)應(yīng)的值螟左,則依舊為原來的值啡浊。
//如果返回null表示添加數(shù)據(jù)成功(不重復(fù)),不重復(fù)(null==null :TRUE)
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
輸出結(jié)果如下:三個(gè)zimug因?yàn)閚ame重復(fù)胶背,另外兩個(gè)被去重巷嚣。并且沒有打亂List的原始順序
Player{name='kobe', age='10000'}
Player{name='james', age='32'}
Player{name='curry', age='30'}
Player{name='zimug', age='27'}
第四種方法
第四種方法實(shí)際上不是新方法,上面的例子都是按某一個(gè)對(duì)象屬性進(jìn)行去重钳吟,如果我們想按照某幾個(gè)元素進(jìn)行去重廷粒,就需要對(duì)上面的三種方法進(jìn)行改造。
我只改造其中一個(gè)红且,另外幾個(gè)改造的原理是一樣的坝茎,就是把多個(gè)比較屬性加起來,作為一個(gè)String屬性進(jìn)行比較暇番。
@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);
}
總結(jié)
到此這篇關(guān)于Java中List集合對(duì)象去重及按屬性去重的8種方法的文章就介紹到這了