首页 > Java > Java集合类ArrayList删除特定元素

Java集合类ArrayList删除特定元素

前一段时间入职新公司,熟悉公司系统原有代码的时候,发现公司代码那个烂啊,系统能正常跑,都不能用侥幸来形容,就是创造了一个奇迹。因为里面不仅没有coding style,而且竟然有很明显的常识性错误。其中当我一眼指出最明显的早就应该出过问题的一个地方,项目组几乎所有成员,是的,几乎全部成员,都说这个还真不知道,涨知识了,那就是:Java集合类ArrayList删除特定元素。发现原来不是所有人都知道怎么做,这难道不是最基础的吗?唉,真不知道这些系统是怎么跑起来的。我们首先看两种错误的写法,第一种:


    @Test
    public void testRemove1() {
        List<String> list = new ArrayList<String>() {
            private static final long serialVersionUID = 1L;

            {
                add("cn");
                add("bridgeli");
                add("blog");
            }
        };
        for (int i = 0, len = list.size(); i < len; i++) {
            String str = list.get(i);
            if ("cn".equals(str)) {
                list.remove(str);
            }
        }
    }

这个写法如果你不知道错在哪,那你得真的好好补基础了。由于这个错误比较明显,所以有人搞了下面这种写法,也是我们公司的同事犯的一个错误:


    @Test
    public void testRemove2() {
        List<String> list = new ArrayList<String>() {
            private static final long serialVersionUID = 1L;

            {
                add("cn");
                add("bridgeli");
                add("blog");
            }
        };
        for (String str : list) {
            if ("cn".equals(str)) {
                list.remove(str);
            }
        }
    }

跑一下这个例子看看,把cn换成blog试试,把String换成Integer试试,出乎不出乎你的意料?下面我们就来简单探究一下原因foreach的原理,其实特别简单:

Java.util.List实现了java.lang.Iterable接口。
jdk api文档中是这样描述Iterable接口的:实现这个接口允许对象成为 “foreach” 语句的目标。看一下Iterable接口并没有发现啥特别之处,只是定义了一个迭代器而已。用javap看一下字节码,你就会发现:foreach语法最终被编译器转为了对Iterator.next()的调用。而作为使用者的我们,jdk并没用向我们暴露这些细节,我们甚至不需要知道Iterator的存在,怎么样编译器厉害吧?如果你还不相信,你可以用Iterator写了个遍历List的方法看一下字节码看看和foreach是不是一样。既然是调用了Iterator.next(),那这个方法有什么特殊吗?看看实现就知道了。


    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

方法的第一行调用了checkForComodification(),而他的实现呢?


    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }

到这应该很明显了吧,看list的remove()实现,每次remove()的时候modCount都会++,而expectedModCount只会在初始化的时候赋过一次值。然后他们的值就不一样了,然后你肯定知道了。看完了错误的写法,我们看看其他的最正确的写法:


    @Test
    public void testRemove3() {
        List<String> list = new ArrayList<String>() {
            private static final long serialVersionUID = 1L;

            {
                add("cn");
                add("bridgeli");
                add("blog");
            }
        };
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String str = iterator.next();
            if ("blog".equals(str)) {
                iterator.remove();
            }
        }
    }

这个为什么不会出错,我相信根据刚才的分析,大家一定可以找到答案。还有为什么说这个最正确呢?因为还有其他的写法,我个人认为那些写法实在太low就不展示了,所以大家今后有这种需求就用这种写法就好了。最后的最后推荐大家看一看阿里巴巴新出的Java开发手册,我相信这些肯定是阿里的同行踩过的坑的总结,里面不仅对coding style做了严格要求,而且还能避免很多常识性错误,而且有些还说明了原因,里面就有对这个问题的说明,所以看一下吧,虽然里面的部分观点你可能不认同,但我相信看完实践一下你会受益良多。

  1. 2017年7月5日16:20 | #1

    哈哈 这个是我的面试题,确实好多人载这上面了

请输入正确的验证码