Java中List集合对象去重及按属性去重的8种方法

一、本文描述

这一篇文章写一下List集合元素去重的8种方法,实际上通过灵活的运用、排列组合不一定是8种,可能有18种方法。

  • 对象元素整体去重的4种方法
  • 按照对象属性去重的4种方法

为了在下文中进行测试内容讲解,我们先做一些初始化数据

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")); // 注意这里名字重复
 playerList.add(new Player("zimug","18")); //注意这里名字和年龄重复
 playerList.add(new Player("zimug","18")); //注意这里名字和年龄重复
 
 }
}

Player对象就是一个普通的java对象,有两个成员变量name与age,实现了带参数构造函数、toString、equals和hashCode方法、以及GET/SET方法。

二、集合元素整体去重

下文中四种方法对List中的String类型以集合元素对象为单位整体去重。如果你的List放入的是Object对象,需要你去实现对象的equals和hashCode方法,去重的代码实现方法和List<String>去重是一样的。

第一种方法

是大家最容易想到的,先把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);
}

控制台打印结果如下:

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

第二种方法

使用就比较简单,先用stream方法将集合转换成流,然后distinct去重,最后在将Stream流collect收集为List。

@Test
void testRemove2() {
 List<String> newList = list.stream().distinct().collect(Collectors.toList());
 
 System.out.println( "去重后的集合: " + newList);
}

控制台打印结果如下:

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

第三种方法

这种方法利用了set.add(T),如果T元素已经存在集合中,就返回false。利用这个方法进行是否重复的数据判断,如果不重复就放入一个新的newList中,这个newList就是最终的去重结果

//三个集合类list、newList、set,能够保证顺序
@Test
void testRemove3() {
 
 Set<String> set = new HashSet<>();
 List<String> newList = new ArrayList<>();
 for (String str :list) {
 if(set.add(str)){ //重复的话返回false
 newList.add(str);
 }
 }
 System.out.println( "去重后的集合: " + newList);
 
}

控制台打印结果和第二种方法一致。

第四种方法

这种方法已经脱离了使用Set集合进行去重的思维,而是使用newList.contains(T)方法,在向新的List添加数据的时候判断这个数据是否已经存在,如果存在就不添加,从而达到去重的效果。

//优化 List、newList、set,能够保证顺序
@Test
void testRemove4() {
 
 List<String> newList = new ArrayList<>();
 for (String cd:list) {
 if(!newList.contains(cd)){ //主动判断是否包含重复元素
 newList.add(cd);
 }
 }
 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()把list集合转换成流
  • 然后用collect及toCollection把流转换成集合
  • 然后剩下的就和第一种方法一样了

前两步不是脱了裤子放屁么?看看就得了,实际应用意义不大,但是如果是为了学习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);
}

控制台打印输出和第一种方法一样。

第三种方法

这种方法也是笔者建议大家使用的一种方法,咋一看好像代码量更大了,但实际上这种方法是应用比较简单的方法。

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

总结

到此这篇关于Java中List集合对象去重及按属性去重的8种方法的文章就介绍到这了

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容