Java中List集合對(duì)象去重及按屬性去重的8種方法

一常柄、本文描述

這一篇文章寫一下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種方法的文章就介紹到這了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末景东,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子奔誓,更是在濱河造成了極大的恐慌斤吐,老刑警劉巖搔涝,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異和措,居然都是意外死亡庄呈,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門派阱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诬留,“玉大人,你說我怎么就攤上這事贫母∥亩遥” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵腺劣,是天一觀的道長(zhǎng)绿贞。 經(jīng)常有香客問我,道長(zhǎng)橘原,這世上最難降的妖魔是什么籍铁? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮趾断,結(jié)果婚禮上拒名,老公的妹妹穿的比我還像新娘。我一直安慰自己芋酌,他們只是感情好增显,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著脐帝,像睡著了一般同云。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上腮恩,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音温兼,去河邊找鬼秸滴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛募判,可吹牛的內(nèi)容都是我干的荡含。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼届垫,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼释液!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起装处,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤误债,失蹤者是張志新(化名)和其女友劉穎浸船,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寝蹈,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡李命,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了箫老。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片封字。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖耍鬓,靈堂內(nèi)的尸體忽然破棺而出阔籽,到底是詐尸還是另有隱情,我是刑警寧澤牲蜀,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布笆制,位于F島的核電站,受9級(jí)特大地震影響各薇,放射性物質(zhì)發(fā)生泄漏项贺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一峭判、第九天 我趴在偏房一處隱蔽的房頂上張望开缎。 院中可真熱鬧,春花似錦林螃、人聲如沸奕删。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)完残。三九已至,卻和暖如春横漏,著一層夾襖步出監(jiān)牢的瞬間谨设,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工缎浇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扎拣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓素跺,卻偏偏與公主長(zhǎng)得像二蓝,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子指厌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容