List集合對象去重及按屬性去重的8種方法-java基礎(chǔ)總結(jié)系列第六篇


最近在寫一些關(guān)于java基礎(chǔ)的文章缎浇,但是我又不想按照教科書的方式去寫知識點的文章骡楼,因為意義不大熔号。基礎(chǔ)知識太多了鸟整,如何將這些知識歸納總結(jié)引镊,總結(jié)出優(yōu)缺點或者是使用場景才是對知識的升華。所以我更想把java相關(guān)的基礎(chǔ)知識進行穿針引線篮条,進行整體上的總結(jié)弟头。

  • 總結(jié)java中創(chuàng)建并寫文件的5種方式
  • 總結(jié)java從文件中讀取數(shù)據(jù)的6種方法
  • 總結(jié)java創(chuàng)建文件夾的4種方法及其優(yōu)缺點
  • 總結(jié)java中刪除文件或文件夾的7種方法
  • 總結(jié)java中文件拷貝剪切的5種方式

比如之前我已經(jīng)寫了上面的這些內(nèi)容,如果對java基礎(chǔ)知識總結(jié)系列感興趣的同學(xué)可以關(guān)注我的博客(文末給出我的博客地址)涉茧。

一赴恨、本文梗概

這一篇文章我想寫一下List集合元素去重的8種方法,實際上通過靈活的運用伴栓、排列組合不一定是8種伦连,可能有18種方法。

  • 對象元素整體去重的4種方法
  • 按照對象屬性去重的4種方法

為了在下文中進行測試內(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對象就是一個普通的java對象惑淳,有兩個成員變量name與age,實現(xiàn)了帶參數(shù)構(gòu)造函數(shù)饺窿、toString歧焦、equals和hashCode方法、以及GET/SET方法肚医。

二绢馍、集合元素整體去重

下文中四種方法對List中的String類型以集合元素對象為單位整體去重向瓷。如果你的List放入的是Object對象,需要你去實現(xiàn)對象的equals和hashCode方法舰涌,去重的代碼實現(xiàn)方法和List<String>去重是一樣的风罩。

第一種方法

是大家最容易想到的,先把List數(shù)據(jù)放入Set舵稠,因為Set數(shù)據(jù)結(jié)構(gòu)本身具有去重的功能,所以再將SET轉(zhuǎn)為List之后就是去重之后的結(jié)果入宦。這種方法在去重之后會改變原有的List元素順序哺徊,因為HashSet本身是無序的,而TreeSet排序也不是List種元素的原有順序乾闰。

@Test
void testRemove1()  {
  /*Set<String> set = new HashSet<>(list);
  List<String> newList = new ArrayList<>(set);*/

  //去重并排序的方法(如果是字符串落追,按字母表排序。如果是對象涯肩,按Comparable接口實現(xiàn)排序)
  //List<String> newList = new ArrayList<>(new TreeSet<>(list));

  //簡寫的方法
  List<String> newList = new ArrayList<>(new HashSet<>(list));

  System.out.println( "去重后的集合: " + newList);
}

控制臺打印結(jié)果如下:

去重后的集合: [kobe, james, zimug, curry]

第二種方法

使用就比較簡單轿钠,先用stream方法將集合轉(zhuǎn)換成流,然后distinct去重病苗,最后在將Stream流collect收集為List疗垛。

@Test
void testRemove2()  {
  List<String> newList = list.stream().distinct().collect(Collectors.toList());

  System.out.println( "去重后的集合: " + newList);
}

控制臺打印結(jié)果如下:

去重后的集合: [kobe, james, curry, zimug]

第三種方法
這種方法利用了set.add(T),如果T元素已經(jīng)存在集合中,就返回false硫朦。利用這個方法進行是否重復(fù)的數(shù)據(jù)判斷贷腕,如果不重復(fù)就放入一個新的newList中,這個newList就是最終的去重結(jié)果

//三個集合類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);

}

控制臺打印結(jié)果和第二種方法一致破婆。

第四種方法
這種方法已經(jīng)脫離了使用Set集合進行去重的思維涮总,而是使用newList.contains(T)方法,在向新的List添加數(shù)據(jù)的時候判斷這個數(shù)據(jù)是否已經(jīng)存在祷舀,如果存在就不添加瀑梗,從而達到去重的效果。

//優(yōu)化 List裳扯、newList夺克、set,能夠保證順序
@Test
void testRemove4() {

  List<String> newList = new  ArrayList<>();
  for (String cd:list) {
    if(!newList.contains(cd)){  //主動判斷是否包含重復(fù)元素
      newList.add(cd);
    }
  }
  System.out.println( "去重后的集合: " + newList);

}

控制臺打印結(jié)果和第二種方法一致嚎朽。

三铺纽、按照集合元素對象屬性去重

其實在實際的工作中,按照集合元素對象整體去重的應(yīng)用的還比較少哟忍,更多的是要求我們按照元素對象的某些屬性進行去重狡门。
看到這里請大家回頭去看一下上文中陷寝,構(gòu)造的初始化數(shù)據(jù)playerList,特別注意其中的一些重復(fù)元素,以及成員變量重復(fù)其馏。

第一種方法
為TreeSet實現(xiàn)Comparator接口凤跑,如果我們希望按照Player的name屬性進行去重,就去在Comparator接口中比較name叛复。下文中寫了兩種實現(xiàn)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());
  });*/
  //將去重之后的結(jié)果打印出來
  new ArrayList<>(playerSet).forEach(System.out::println);
}

輸出結(jié)果如下:三個zimug因為name重復(fù)仔引,另外兩個被去重。但是因為使用到了TreeSet褐奥,list中元素被重新排序咖耘。

Player{name='curry', age='30'}
Player{name='james', age='32'}
Player{name='kobe', age='10000'}
Player{name='zimug', age='27'}

第二種方法
這種方法是網(wǎng)上很多的文章中用來顯示自己很牛的方法,但是在筆者看來有點脫了褲子放屁撬码,多此一舉儿倒。既然大家都說有這種方法,我不寫好像我不牛一樣呜笑。我為什么說這種方法是“脫了褲子放屁”夫否?

  • 首先用stream()把list集合轉(zhuǎn)換成流
  • 然后用collect及toCollection把流轉(zhuǎn)換成集合
  • 然后剩下的就和第一種方法一樣了

前兩步不是脫了褲子放屁么?看看就得了叫胁,實際應(yīng)用意義不大凰慈,但是如果是為了學(xué)習(xí)Stream流的使用方法,搞出這么一個例子還是有可取之處的驼鹅。

@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);
}

控制臺打印輸出和第一種方法一樣溉瓶。

第三種方法

這種方法也是筆者建議大家使用的一種方法,咋一看好像代碼量更大了谤民,但實際上這種方法是應(yīng)用比較簡單的方法堰酿。

Predicate(有人管這個叫斷言,從英文的角度作為名詞可以翻譯為謂詞张足,作為動詞可以翻譯為斷言)触创。謂詞就是用來修飾主語的,比如:喜歡唱歌的小鳥为牍,喜歡唱歌就是謂詞哼绑,用來限定主語的范圍。所以我們這里是用來filter過濾的碉咆,也是用來限制主語范圍的抖韩,所以我認為翻譯為謂詞更合適。隨便吧疫铜,看你怎么覺得怎么理解合理茂浮、好記,你就怎么來。

  • 首先我們定義一個謂詞Predicate用來過濾席揽,過濾的條件是distinctByKey顽馋。謂詞返回ture元素保留,返回false元素被過濾掉幌羞。
  • 當然我們的需求是過濾掉重復(fù)元素寸谜。我們?nèi)ブ剡壿嬍峭ㄟ^map的putIfAbsent實現(xiàn)的。putIfAbsent方法添加鍵值對属桦,如果map集合中沒有該key對應(yīng)的值熊痴,則直接添加,并返回null聂宾,如果已經(jīng)存在對應(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方法添加鍵值對许赃,如果map集合中沒有該key對應(yīng)的值止喷,則直接添加,并返回null混聊,如果已經(jīng)存在對應(yīng)的值弹谁,則依舊為原來的值。
  //如果返回null表示添加數(shù)據(jù)成功(不重復(fù))句喜,不重復(fù)(null==null :TRUE)
  return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}

輸出結(jié)果如下:三個zimug因為name重復(fù)预愤,另外兩個被去重。并且沒有打亂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);
}

歡迎關(guān)注我的博客,里面有很多精品合集

  • 本文轉(zhuǎn)載注明出處(必須帶連接来惧,不能只轉(zhuǎn)文字):字母哥博客冗栗。

覺得對您有幫助的話,幫我點贊、分享贞瞒!您的支持是我不竭的創(chuàng)作動力偶房! 。另外军浆,筆者最近一段時間輸出了如下的精品內(nèi)容棕洋,期待您的關(guān)注。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末乒融,一起剝皮案震驚了整個濱河市掰盘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赞季,老刑警劉巖愧捕,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異申钩,居然都是意外死亡次绘,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門撒遣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來邮偎,“玉大人,你說我怎么就攤上這事义黎『探” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵廉涕,是天一觀的道長泻云。 經(jīng)常有香客問我,道長狐蜕,這世上最難降的妖魔是什么宠纯? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮层释,結(jié)果婚禮上征椒,老公的妹妹穿的比我還像新娘。我一直安慰自己湃累,他們只是感情好勃救,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著治力,像睡著了一般蒙秒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宵统,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天晕讲,我揣著相機與錄音覆获,去河邊找鬼。 笑死瓢省,一個胖子當著我的面吹牛弄息,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播勤婚,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼摹量,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了馒胆?” 一聲冷哼從身側(cè)響起缨称,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎祝迂,沒想到半個月后睦尽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡型雳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年当凡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纠俭。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡沿量,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出柑晒,到底是詐尸還是另有隱情欧瘪,我是刑警寧澤眷射,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布匙赞,位于F島的核電站,受9級特大地震影響妖碉,放射性物質(zhì)發(fā)生泄漏涌庭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一欧宜、第九天 我趴在偏房一處隱蔽的房頂上張望坐榆。 院中可真熱鬧,春花似錦冗茸、人聲如沸席镀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽豪诲。三九已至,卻和暖如春挂绰,著一層夾襖步出監(jiān)牢的瞬間屎篱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留交播,地道東北人重虑。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像秦士,于是被迫代替她去往敵國和親缺厉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344