飞道的博客

面试官提问:如何去掉list集合中重复的元素?

475人阅读  评论(0)

每天早上七点三十,准时推送干货

一、问题由来

在实际开发的时候,我们经常会碰到这么一个困难:一个集合容器里面有很多重复的对象,里面的对象没有主键,但是根据业务的需求,实际上我们需要根据条件筛选出没有重复的对象

比较暴力的方法,就是根据业务需求,通过两层循环来进行判断,没有重复的元素就加入到新集合中,新集合中已经有的元素就跳过。

操作例子如下,创建一个实体对象PenBean,代码如下:


   
  1. /**
  2.  * 笔实体
  3.  */
  4. public class PenBean {
  5.      /**类型*/
  6.     private String  type;
  7.      /**颜色*/
  8.     private String color;
  9.      //... 省略 setter 和 getter
  10.     public PenBean(String  type, String color) {
  11.         this. type =  type;
  12.         this.color = color;
  13.     }
  14.     @Override
  15.     public String toString() {
  16.          return  "PenBean{" +
  17.                  "type='" +  type +  '\'' +
  18.                  ", color='" + color +  '\'' +
  19.                  '}';
  20.     }
  21. }

测试 demo,如下:


   
  1. public static void main(String[] args) {
  2.      //添加信息,PenBean中没有主键
  3.     List<PenBean> penBeanList =  new ArrayList<PenBean>();
  4.     penBeanList.add( new PenBean( "铅笔", "black"));
  5.     penBeanList.add( new PenBean( "铅笔", "white"));
  6.     penBeanList.add( new PenBean( "铅笔", "black"));
  7.     penBeanList.add( new PenBean( "中性笔", "white"));
  8.     penBeanList.add( new PenBean( "中性笔", "white"));
  9.      //新数据
  10.     List<PenBean> newPenBeanList =  new ArrayList<PenBean>();
  11.      //传统重复判断
  12.      for (PenBean penBean : penBeanList) {
  13.          if(newPenBeanList.isEmpty()){
  14.             newPenBeanList.add(penBean);
  15.         } else{
  16.             boolean isSame =  false;
  17.              for (PenBean newPenBean : newPenBeanList) {
  18.                  //依靠type、color来判断,是否有重复元素
  19.                  //如果新集合包含元素,直接跳过
  20.                  if(penBean.getType().equals(newPenBean.getType()) && penBean.getColor().equals(newPenBean.getColor())){
  21.                     isSame =  true;
  22.                      break;
  23.                 }
  24.             }
  25.              if(!isSame){
  26.                 newPenBeanList.add(penBean);
  27.             }
  28.         }
  29.     }
  30.      //输出结果
  31.     System.out. println( "=========新数据======");
  32.      for (PenBean penBean : newPenBeanList) {
  33.         System.out. println(penBean.toString());
  34.     }
  35. }

输出结果:


   
  1. =========新数据======
  2. PenBean{ type= '铅笔', color= 'black'}
  3. PenBean{ type= '铅笔', color= 'white'}
  4. PenBean{ type= '中性笔', color= 'white'}

一般处理数组类型的对象时,可以通过这种方法来对数组元素进行去重操作,以筛选出没有包含重复元素的数组。

那有没有更加简洁的写法呢?

答案肯定是有的,List中的contains()方法就是!

二、利用list中contains方法去重

在使用contains()之前,必须要对PenBean类重写equals()方法,为什么要这么做?等会会详细解释!

我们先在PenBean类中重写equals()方法,内容如下:


   
  1. @Override
  2. public boolean equals(Object o) {
  3.      if (this == o)  return  true;
  4.      if (o == null || getClass() != o.getClass())  return  false;
  5.     PenBean penBean = (PenBean) o;
  6.     //当type、color 内容都相等的时候,才返回true
  7.      return Objects.equals( type, penBean. type) &&
  8.             Objects.equals(color, penBean.color);
  9. }

修改测试 demo,如下:


   
  1. public static void main(String[] args) {
  2.      //添加信息
  3.     List<PenBean> penBeanList =  new ArrayList<PenBean>();
  4.     penBeanList.add( new PenBean( "铅笔", "black"));
  5.     penBeanList.add( new PenBean( "铅笔", "white"));
  6.     penBeanList.add( new PenBean( "铅笔", "black"));
  7.     penBeanList.add( new PenBean( "中性笔", "white"));
  8.     penBeanList.add( new PenBean( "中性笔", "white"));
  9.      //新数据
  10.     List<PenBean> newPenBeanList =  new ArrayList<PenBean>();
  11.      //使用contain判断,是否有相同的元素
  12.      for (PenBean penBean : penBeanList) {
  13.          if(!newPenBeanList.contains(penBean)){
  14.             newPenBeanList.add(penBean);
  15.         }
  16.     }
  17.      //输出结果
  18.     System.out. println( "=========新数据======");
  19.      for (PenBean penBean : newPenBeanList) {
  20.         System.out. println(penBean.toString());
  21.     }
  22. }

输出结果如下:


   
  1. =========新数据======
  2. PenBean{ type= '铅笔', color= 'black'}
  3. PenBean{ type= '铅笔', color= 'white'}
  4. PenBean{ type= '中性笔', color= 'white'}

如果PenBean对象不重写equals()contains()方法的都是false!新数据与源数据是一样的,并不能达到我们想要除去重复元素的目的

那么contains()是怎么做到,判断一个集合里面有相同的元素呢?

我们打开ArrayListcontains()方法,源码如下:


   
  1. public boolean contains(Object o) {
  2.      return indexOf(o) >=  0;
  3. }

找到indexOf(o)方法,继续往下看,源码如下:


   
  1. public  int indexOf(Object o) {
  2.      if (o == null) {
  3.          for ( int i =  0; i < size; i++)
  4.              if (elementData[i]==null)
  5.                  return i;
  6.     }  else {
  7.          for ( int i =  0; i < size; i++)
  8.             //对象通过 equals 方法,判断是否相同
  9.              if (o.equals(elementData[i]))
  10.                  return i;
  11.     }
  12.      return  -1;
  13. }

此时,非常清晰了,如果传入的对象是null,for循环判断数组中的元素是否有null,如果有就返回下标;如果传入的对象不是null,通过对象的equals()方法,for循环判断是否有相同的元素,如果有就返回下标!

如果是数组返回的下标,肯定是大于0,否则返回-1!

这就是为什么在List中使用contains()方法,对象需要重写equals()方法的原因!

三、java 8中去重操作

当然,有些朋友可能会想到 JDK1.8 中的流式写法,例如 jdk1.8 中的集合元素去重写法如下:


   
  1. public static void main(String[] args) {
  2.      //添加信息
  3.     List<PenBean> penBeanList =  new ArrayList<PenBean>();
  4.     penBeanList.add( new PenBean( "铅笔", "black"));
  5.     penBeanList.add( new PenBean( "铅笔", "white"));
  6.     penBeanList.add( new PenBean( "铅笔", "black"));
  7.     penBeanList.add( new PenBean( "中性笔", "white"));
  8.     penBeanList.add( new PenBean( "中性笔", "white"));
  9.      //使用java8新特性stream进行List去重
  10.     List<PenBean> newPenBeanList = penBeanList.stream().distinct().collect(Collectors.toList());
  11.      //输出结果
  12.     System.out. println( "=========新数据======");
  13.      for (PenBean penBean : newPenBeanList) {
  14.         System.out. println(penBean.toString());
  15.     }
  16. }

利用 jdk1.8 中提供的Stream.distinct()列表去重,Stream.distinct()使用hashCode()equals()方法来获取不同的元素,因此使用这种写法,对象需要重写hashCode()equals()方法!

PenBean对象重写hashCode()方法,代码如下:


   
  1. @Override
  2. public  int hashCode() {
  3.      return Objects.hash( type, color);
  4. }

在运行测试demo,结果如下:


   
  1. =========新数据======
  2. PenBean{ type= '铅笔', color= 'black'}
  3. PenBean{ type= '铅笔', color= 'white'}
  4. PenBean{ type= '中性笔', color= 'white'}

即可实现集合元素的去重操作!

那为什么当我们使用String类型的对象作为集合元素时,没有重写

因为 java 中String原生类,已经重写好了,源码如下:


   
  1. public final class String
  2. implements java.io.Serializable, Comparable<String>, CharSequence {
  3.  
  4.  @Override
  5.  public boolean equals(Object anObject) {
  6.          if (this == anObject) {
  7.              return  true;
  8.         }
  9.          if (anObject instanceof String) {
  10.             String anotherString = (String)anObject;
  11.              int n = value.length;
  12.              if (n == anotherString.value.length) {
  13.                 char v1[] = value;
  14.                 char v2[] = anotherString.value;
  15.                  int i =  0;
  16.                 while (n-- !=  0) {
  17.                      if (v1[i] != v2[i])
  18.                          return  false;
  19.                     i++;
  20.                 }
  21.                  return  true;
  22.             }
  23.         }
  24.          return  false;
  25.     }
  26.  
  27.  @Override
  28.  public  int hashCode() {
  29.      int h = hash;
  30.      if (h ==  0 && value.length >  0) {
  31.         char val[] = value;
  32.          for ( int i =  0; i < value.length; i++) {
  33.             h =  31 * h + val[i];
  34.         }
  35.         hash = h;
  36.     }
  37.      return h;
  38. }
  39. }

四、HashSet去重操作

在上面的分享中,我们介绍了 List 的集合去重操作!其中网友还提到了HashSet可以实现元素的去重!

的确,HashSet集合天然支持元素不重复!

实践代码如下!

还是先创建一个对象PenBean,同时重写Object中的equals()hashCode()方法,如下:


   
  1. /**
  2.  * 笔实体
  3.  */
  4. public class PenBean {
  5.      /**类型*/
  6.     private String  type;
  7.      /**颜色*/
  8.     private String color;
  9.      //... 省略 setter 和 getter
  10.     public PenBean(String  type, String color) {
  11.         this. type =  type;
  12.         this.color = color;
  13.     }
  14.     @Override
  15.     public String toString() {
  16.          return  "PenBean{" +
  17.                  "type='" +  type +  '\'' +
  18.                  ", color='" + color +  '\'' +
  19.                  '}';
  20.     }
  21.  
  22.  @Override
  23.  public boolean equals(Object o) {
  24.        if (this == o)  return  true;
  25.        if (o == null || getClass() != o.getClass())  return  false;
  26.       PenBean penBean = (PenBean) o;
  27.        //当type、color 内容都相等的时候,才返回true
  28.        return Objects.equals( type, penBean. type) &&
  29.           Objects.equals(color, penBean.color);
  30.  }
  31.  
  32.  @Override
  33.  public  int hashCode() {
  34.      return Objects.hash( type, color);
  35.  }
  36.   
  37. }

创建测试 demo,如下:


   
  1. public static void main(String[] args) {
  2.      //添加信息
  3.     List<PenBean> penBeanList =  new ArrayList<PenBean>();
  4.     penBeanList.add( new PenBean( "铅笔", "black"));
  5.     penBeanList.add( new PenBean( "铅笔", "white"));
  6.     penBeanList.add( new PenBean( "铅笔", "black"));
  7.     penBeanList.add( new PenBean( "中性笔", "white"));
  8.     penBeanList.add( new PenBean( "中性笔", "white"));
  9.      //新数据
  10.     List<PenBean> newPenBeanList =  new ArrayList<PenBean>();
  11.      //set去重
  12.     HashSet<PenBean> set =  new HashSet<>(penBeanList);
  13.     newPenBeanList.addAll(set);
  14.      //输出结果
  15.     System.out. println( "=========新数据======");
  16.      for (PenBean penBean : newPenBeanList) {
  17.         System.out. println(penBean.toString());
  18.     }
  19. }

输出结果如下:


   
  1. =========新数据======
  2. PenBean{ type= '铅笔', color= 'white'}
  3. PenBean{ type= '铅笔', color= 'black'}
  4. PenBean{ type= '中性笔', color= 'white'}

很明细,返回的新集合没有重复元素!

HashSet是怎么做的的呢?

打开HashSet的源码,查看我们传入的构造方法如下:


   
  1. public HashSet(Collection<? extends E> c) {
  2.      map =  new HashMap<>(Math.max(( int) (c.size()/ .75f) +  116));
  3.     addAll(c);
  4. }

很显然,首先创建了一个HashMap对象,然后调用addAll()方法,继续往下看这个方法!


   
  1. public boolean addAll(Collection<? extends E> c) {
  2.     boolean modified =  false;
  3.      for (E e : c)
  4.          if (add(e))
  5.             modified =  true;
  6.      return modified;
  7. }

首先遍历List中的元素,然后调用add()方法,这个方法,源码如下:


   
  1. public boolean add(E e) {
  2.      return  map.put(e, PRESENT)==null;
  3. }

其实,就是向HashMap对象中插入元素,其中PRESENT是一个new Object()常量!

private static final Object PRESENT = new Object();

到这里就基本很清楚了,向HashSet中添加元素,其实等同于


   
  1. Map<Object,Object>  map =  new HashMap<Object,Object>();
  2. map.put(e, new Object); //e表示要插入的元素

其中插入的元素e,就是HashMap中的key

我们知道HashMap,是通过equals()hashCode()来判断插入的key是否为同一个key,因此,当我们对PenBean对象进行重写equals()hashCode()时,保证判断是同一个key时,就可以达到元素去重的目的!

最后,对已经去重的集合HashSet,再通过ArrayList中的addAll()方法进行包装,即可得到我们想要的不包含重复元素的数据

五、参考

JDK1.8-源码分析

最后说两句(求关注)

最近大家应该发现微信公众号信息流改版了吧,再也不是按照时间顺序展示了。这就对阿粉这样的坚持的原创小号主,可以说非常打击,阅读量直线下降,正反馈持续减弱。

所以看完文章,哥哥姐姐们给阿粉来个在看吧,让阿粉拥有更加大的动力,写出更好的文章,拒绝白嫖,来点正反馈呗~。

如果想在第一时间收到阿粉的文章,不被公号的信息流影响,那么可以给Java极客技术设为一个星标

最后感谢各位的阅读,才疏学浅,难免存在纰漏,如果你发现错误的地方,由于本号没有留言功能,还请你在后台留言指出,我对其加以修改。

最后谢谢大家支持~

最最后,重要的事再说一篇~

快来关注我呀~
快来关注我呀~
快来关注我呀~

< END >

如果大家喜欢我们的文章,欢迎大家转发,点击在看让更多的人看到。也欢迎大家热爱技术和学习的朋友加入的我们的知识星球当中,我们共同成长,进步。


转载:https://blog.csdn.net/javageektech/article/details/107852903
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场