Java ArrayList 踩坑记录

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Java ArrayList 踩坑记录

Java ArrayList 踩坑记录

作者: 等你归去来

链接:https://www.cnblogs.com/yougewe/(点击阅读原文前去围观)

      做编程的一个常识:不要在循环过程中删除元素本身(至少是我个人的原则)。否则将发生不可预料的问题。

  而最近,看到一个以前的同学写的一段代码就是在循环过程中删除元素,我很是纳闷啊。然后后来决定给他改掉。然后引发了另外的惨案。

  原来的代码是这样的:


public ListA getUserDebitCard(A cond) {
        ListA list = userService.getCard(cond);
        ListA result = null;
        if(list! = null && list.size()  0){
            Collections.sort(list, new ComparatorA(){  
              public int compare(A b1, A b2) {  
              //按时间排序
                if(Integer.valueOf(b1.getAddTime())  Integer.valueOf(b2.getAddTime())){  
                   return -1;  
                  }   
                  return 1;  
                }
             });
             A bean = getA(cond);
             result = new ArrayListA();
             if(bean!=null){
                 for (int i = 0; i  list.size(); i++) {
                    if(list.get(i).getCardNum().equals(bean.getCardNum())){
                        list.get(i).setAs(1);
                        result.add(list.get(i));
                        list.remove(i);
                    }else{
                        list.get(i).setAs(0);
                    }
                 }
             }    
            result.addAll(list);
         }
        return result;
    }

看了如上代码,我很是郁闷,然后给改成如下:


public ListA getUserDebitCard(A cond) {
        ListA list=userService.getCard(cond);
        ListA result=null;
        if(list!=null && list.size()0){
            Collections.sort(list, new ComparatorA(){  
              public int compare(A b1, A b2) {  
              //按时间排序
                if(Integer.valueOf(b1.getAddTime())  Integer.valueOf(b2.getAddTime())){  
                   return -1;  
                  }   
                  return 1;  
                }
             });
             A bean = getA(cond);
             result=new ArrayList();
             if(bean != null){
                 // 将上次的卡放置在第一位
                 Integer lastAIndex = 0;
                 Integer listSize = list.size();
                 if(listSize  0) {
                     for (int i = 0; i  listSize; i++) {
                         if (list.get(i).getCardNum().equals(bean.getCardNum())) {
                             list.get(i).setAs(1);
                             result.add(list.get(i));        //将排在首位的元素先添加好,并记录下index
                             lastAIndex = i;
//                             list.remove(i);                //循环过程中删除元素是危险的
                         } else {
                             list.get(i).setAs(0);
                         }
                     }
                     list.remove(lastAIndex);
                 }
             }    
             result.addAll(list);                            //在循环外删除元素,以为万事大吉,结果悲剧了,这里居然添加了两个元素进来                
         }
        return result;
    }

      这下出事了,原本只有一个元素的result,现在变成了两个了,这是为什么呢?妈蛋,我明明已经remove掉了啊。

  也想过百度一下,但是木有搞定啊。然后,拿出看家绝招,断点调试,进入list.remove(Integer) 方法。其源码如下:


/**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * tti/tt such that
     * tt(o==null ? get(i)==null : o.equals(get(i)))/tt
     * (if such an element exists).  Returns tttrue/tt if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return tttrue/tt if this list contained the specified element
     */
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index  size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index  size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

      原来,由于我使用Integer作为删除元素的条件,这里把Integer当作一个元素去比较了,而并不是平时我们以为的自动拆装箱变为int了。 而进入这个方法的意思,是要找到和元素内容相等的元素,然后删除它。而我给的是一个索引,自然就不相等了,没有删除也是自然了,因为就会看到重复的元素出来了。

  我们再来看一下根据索引删除元素的源码,对比一下:


/**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved  0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

      从这里我们可以看到,根据索引删除ArrayList元素的原理是,将原来的数组排除要删除的索引,然后复制到新的数组去,然后将最后一个元素置为null,留给GC回收。并把删除的元素返回。

  这样,一下就明白了删除ArrayList元素到底是咋么一回事了。修复方案就简单了,将Integer替换为int即可:


// Integer lastAIndex = 0;   //替换成int
        int lastAIndex = 0;

  当然了,最后,我还是换成了另一个更稳妥的方案了,用另一个容器接收最终的结果


public ListA getUserDebitCard(A cond) {
        ListA list = userService.getCard(cond);
        ListA result = null;
        if(list! = null && list.size()  0){
            Collections.sort(list, new ComparatorA(){  
              public int compare(A b1, A b2) {  
              //按时间排序
                if(Integer.valueOf(b1.getAddTime())  Integer.valueOf(b2.getAddTime())){  
                   return -1;  
                  }   
                  return 1;  
                }
             });
             A bean = getA(cond);
             result = new ArrayList();
             if(bean != null){
                 // 将上次的卡放置在第一位
                 for (A card1 : list) {
                     if (card1.getCardNum().equals(bean.getCardNum())) {
                         card1.setAs(1);
                         result.add(0, card1);    //直接插入第一位即可
//                         list.remove(i);                //循环过程中删除元素是危险的
                     } else {
                         card1.setAs(0);
                         result.add(card1);
                     }
                 }
             }                
         }
        return result;
    }

      虽然自动拆装箱很方便,也很实用,但是有时一不小心就会把自己给埋坑里了,当心了。尤其是针对线上问题。

  毕竟,代码中的一点点小问题,一到线上就会被无限放大,不可掉以轻心啊!

Java ArrayList 踩坑记录

推荐作品

  ●  创业公司的技术总监,去上市公司面试,结果凉了

  ●  java程序员面试中最容易被问到的18个算法题

  ●  细思极恐-你真的会写java吗?

  ●  实战:上亿数据如何秒查

  ●  华为Java编程军规,代码验收标准

  ●  如何准备Java初级和高级的技术面试

  ●  我与程序猿相处的一周

  ●  为什么前后端分离了,你比从前更痛苦?

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Java ArrayList 踩坑记录


 上一篇
在Java中如何优雅地判空 在Java中如何优雅地判空
本文系投稿,本文作者:李良逸个人主页:http://blog.imuxuan.com 判空灾难 作为搬砖党的一族们,我们对判空一定再熟悉不过了,不要跟我说你很少进行判空,除非你喜欢NullPointerException。 不过Nul
下一篇 
Java核心数据结构(List、Map、Set)原理与使用技巧 Java核心数据结构(List、Map、Set)原理与使用技巧
JDK提供了一组主要的数据结构实现,如List、Map、Set等常用数据结构。这些数据都继承自java.util.Collection接口,并位于java.util包内。 一、List接口最重要的三种List接口实现:ArrayList、V