动态删除集合遇到的一些问题理解

  这篇文章主要介绍在循环中动态删除集合(数组)元素遇到的问题:结果与实际期望的不符。待会看个例子,以及产生这个问题的原因和解决办法。

  实例场景一:

public class Test {
    public static void printList(List list) {
        for(int i=0;i) {
            System.out.println(list.get(i));
        }
    }
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Boolean(true));
        list.add(new Boolean(false));
        list.add(new Boolean(false));
        list.add(new Boolean(false));
        for(int i=0;i) {
            if(list.get(i)) {
                list.remove(i);
            }
        }
        printList(list);
    }
}
//output:
//false
//false
//false

   这上面这个例子里,我们对判断list集合中的元素如果为true,就删掉这个元素。这时集合中只有第一个元素为true,所以删了它还有3个false元素,结果如我们所预想,接着对上面的list添加元素做些改变,在看看结果:

  

public class Test {
    public static void printList(List list) {
        for(int i=0;i) {
            System.out.println(list.get(i));
        }
    }
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Boolean(true));
        list.add(new Boolean(true));//仅在这里做了处理
        list.add(new Boolean(false));
        list.add(new Boolean(false));
        for(int i=0;i) {
            if(list.get(i)) {
                list.remove(i);
            }
        }
        printList(list);
    }
}
//output:
//true
//false
//false

 

  这一段代码更上面相比,仅仅将list集合中index = 1的false改成了true,照理说这一点小改动无伤大雅,但输出结果却与我们期盼的不一致:为什么不是false,false?为什么角标为0、1号的元素只删了一个,而不是全删呢?我们对循环过程进行断点调试,结果就一目了然了:由于角标为0的元素为true,所以它首当其冲的要被删掉,这一点没什么疑虑,但由于0号位元素被删除,导致list.size()由4变成了3,此时的list为(true,false,false)。在第二轮循环体中,i已经自加完毕,值变成了1,所以list.gei(1)访问的(true,false,false)中的第二个元素,第一个true被直接跳过去了,导致它没被判断删除,捡回了一命。而后面的循环又奈我(false)何,而这个循环只循环了3次。问题已经分析出来了,现在怎么解决这个问题呢?难道万能经典的for循环解决不了这个问题吗?要知道我对它情有独钟啊!好吧,我们分析解决问题思路:首先在之前的for循序中,每删除一个元素,list.size()就减1,但进入下轮循环时,i又已经自增了一个1,这样下去势必导致循环次数的较少,我们的目的是不管他是否删除了元素,他都要循环最原始我们想循环的次数(4)。于是在这里设下判断:当要删除集合元素时(list.size()-1),i就不自增,当不删除集合元素时,i才自增;这样就可以控制循环的次数更最原始的循环次数一致了:

         

public class Test {
    public static void printList(List list) {
        for(int i=0;i) {
            System.out.println(list.get(i));
        }
    }
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Boolean(true));
        list.add(new Boolean(true));//仅在这里做了处理
        list.add(new Boolean(false));
        list.add(new Boolean(false));
        for(int i=0;i<list.size();) {
            if(list.get(i)) {
                list.remove(i);
            }else {
                i++;
            }
        }
        /*第二种写法
        for(int i=0;i*/
        
        printList(list);
    }
}
//output:
//false
//false

 

 

 

 

         通过这种写法,可以把{true,true,false,false}的第二个true的判断给补上去。 结果也就恢复正常了,看来写法丰富的for循环还是可以解决不少问题的。有时你遇到这种问题,想着换这一种遍历方式是不是就能避免呢,例如用迭代器(iterator).

 

public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Boolean(true));
        list.add(new Boolean(true));//仅在这里做了处理
        list.add(new Boolean(false));
        list.add(new Boolean(false));
        Iterator it = list.iterator();
        while(it.hasNext()) {
            if(it.next()) {
                it.remove();
            }
        }
        printList(list);
    }

 

 

输出结果也是false,false。从这可以看出,迭代器帮我们解决了刚刚遇到的问题。而且它的写法更简单。看来我们又得庆幸多了一条解决之道。但在庆幸的同时,是否会好奇迭代器是怎么帮我们解决的呢?反正我闲的蛋疼,抱着能看懂多少算多少的态度去分析了源码,在此向大家汇报一下:

  1.Iterator是一个接口,由于我们这里的list实际上是一个ArrayList,那我们就直接在ArrayList.class这里找,一下是类里面我们用到的几个方法:

 

public Iterator iterator() {
        return new Itr();
    }

    /**
     * 能理解的就写注释,不能理解的不理会了,请原谅我太菜了。。
     */
    private class Itr implements Iterator {
        int cursor;       // 返回下一个元素的索引
        int lastRet = -1; // 当前正在操作的元素的索引
        int expectedModCount = modCount;

        Itr() {}
        //调这个方法时,注意cursor与lastRet值都没有变化,可以理解游标压根就不移动,底下的next,remove()
        //才去改变这两个值,不更改集合的情况下:cursor初始为0,每次move()后,cursor加1,move()四次后,cursor=4,
        //所以第五次进去while()循环判断,返回false。
        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            //先判断hasNext(),再进入这个next(),底下这两个判断成立的情况下,hasNext()都会返回false,
            //所以在hasNext()= true时,这两个if是不会进去的。
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            //从这里可以看出每次move,cursor+1,此时cursor表示的是下一个元素的索引,所以它的值先行加了1,
            //而我们要取的是集合中索引为0的元素,也就是lastRet = cursor(这个cursor是还未加1之前的值,这个很重要)=0
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                
                //从实例理解:在我们的例子中要删除集合索引为1的元素,此时lastRet=1,删除后,我们将要操作的下一个元素
                //索引cursor 赋值为 1;从而保证了下一次调next()方法时lastRet = 1(看上一行注释括号里的内容).因此当集合中只有3个元素时
                //它还是从第二个元素操作起。跟我们for循环时,如果删除元素,那一次循环i就不自增,达到同一个效果。
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
       
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

 

 

 

                迭代器的实现过程在注释写明了,大家可以看看。至此,先前的问题也算水落石出了。还有一点值得一提:Java数组长度是不可变的,而js 里面数组长度是可以动态添加的,当你在动态删除js数组的内容时,也会遇到刚提到的问题,这是你可以考虑用上面提到的for循环写法来解决,毕竟这时Java提供的迭代器是帮不上忙的。                                                                                                                                                       

 

你可能感兴趣的:(动态删除集合遇到的一些问题理解)