阿里巴巴为什么这样强制从List中删除元素

Posted 漫思

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了阿里巴巴为什么这样强制从List中删除元素相关的知识,希望对你有一定的参考价值。

阿里巴巴为什么这样强制从List中删除元素

 

还是先举个例子,你侄女对天文知识感兴趣,然后你就用程序写了太阳系九大星系(水星、金星、地球、火星、木星、土星、天王星、海王星、冥王星)的运行轨迹图,然后拿给侄女看。然后她说错了错了,你的知识太旧了,多了一颗星。根据2006年8月24日国际天文联合大会召开,在会议上经过投票表决,冥王星被降级为矮行星,太阳系目前只剩下八颗行星。所以你需要删除一颗星。这个时候你打开电脑开始删除冥王星。

你从下面List中删除一颗星。

List<String> tempList = Arrays.asList("水星","金星","地球","火星",
"木星","土星","天王星","海王星","冥王星","冥王星");

怎么删除List中的冥王星呢?[PS为了演示某些删除方法不可靠,重复写了冥王星] 。

先写一段阿里规约:

【强制】不要再foreach循环里进行元素的remove/add操作,remove元素请使用Iterator方式,如果并发的操作,需要对Iterator对象加锁。

好了,那就让我们来写所有可能删除元素的方法

1:普通的for循环的删除(不可靠)。

List<String> list = new ArrayList(tempList);
         for (int i = 0; i < list.size(); i++) 
            String str = list.get(i);
            if ("冥王星".equals(str)) 
                list.remove(i);
            
        
        System.out.println(list);

运行结果如下:

[水星, 金星, 地球, 火星, 木星, 土星, 天王星,    海王星, 冥王星]

奇了怪了,没删除干净?

问题出在 list.size(),因为 list.size() 和 i 都是动态变化的,i 的值一直在累加,list.size() 一直在减少,所以 list 就会早早结束了循环。所以这种方式虽然不会报错,但存在隐患,并且不容易被察觉,不建议使用。

2:普通的for循环提取变量进行删除(这个更不可靠,会报错)。

List<String> list = new ArrayList(tempList);
        int size = list.size();
        for (int i = 0; i < size; i++) 
            String result = list.get(i);
            if ("冥王星".equals(result)) 
                list.remove(i);
            
        
        System.out.println(list);

结果如下:

这更不对了,一下子搞出个下标越界。

因为 size 变量是固定的,但 list 的实际大小是不断减小的,而 i 的大小是不断累加的,一旦 i >= list 的实际大小肯定就异常了。

3:普通的for循环倒叙删除(这个用法可以)。

for (int i = list.size() - 1; i > 0; i--) 
            String result = list.get(i);
            if ("冥王星".equals(result)) 
                list.remove(i);
            
        
        System.out.println(list);

运行结果如下:

[水星, 金星, 地球, 火星, 木星, 土星, 天王星, 海王星]

4:使用增强的for循环删除(会抛出异常,不推荐,注意我这次为了演示效果,把行星的顺序换一下),不少开发者喜欢用这种方式。

List<String> tempList = Arrays.asList("水星","金星","地球","火星",
"冥王星","土星","天王星","海王星","冥王星","木星");
         List<String> list = new ArrayList(tempList);
        for (String item : list) 
            if ("冥王星".equals(item)) 
                list.remove(item);
            
        
        System.out.println(list);

结果如下:

奇了怪了,又抛异常了。不过这次的异常和上面的下标异常不太一样,这次是:

java.util.ConcurrentModificationException

这个是集合操作中很常见的异常之一,即并发修改异常!

增强的 for循环,其内部是调用的 Iterator 的方法,取下个元素的时候都会去判断要修改的数量(modCount)和期待修改的数量(expectedModCount)是否一致,不一致则会报错,而 ArrayList 中的 remove 方法并没有同步期待修改的数量(expectedModCount)值,所以会抛异常了。

5、迭代器循环迭代器删除(可靠,也是十分推荐的用法)。

Iterator<String> iterator = list.iterator();
        while (iterator.hasNext())
            String item = iterator.next() ;
            if ("冥王星".equals(item))
                iterator.remove();
            
        
        System.out.println(list);

结果如下,十分完美和正确:

[水星, 金星, 地球, 火星, 土星, 天王星, 海王星, 木星]

这是因为迭代器中的 remove 方法将期待修改的数量(expectedModCount)值进行了同步。

 

6:迭代器循环集合删除(这个可能很多开发者也会这样写,也可能会抛出异常的)。

Iterator<String> iterator = list.iterator();
        while (iterator.hasNext())
            String item = iterator.next() ;
            if ("冥王星".equals(item))
                list.remove(item);
            
        
        System.out.println(list);

结果如下:

7:Stream filter 过滤(十分推荐,当然使用这个删除需要JDK的环境在8及其8以上的版本)。

list = list.stream().filter(item -> !"冥王星".equals(item)).
 collect(Collectors.toList());
 System.out.println(list);

结果如下,十分完美和正确:

这个方法利用了 Stream 的筛选功能,快速过滤所需要的元素,虽然不是进行集合删除,但达到了同样的目的,这种方法要更简洁

看了上面的几个例子,相信你熟悉了List删除元素的用法了,希望你看了上面的例子,开发的时候不会再犯错了。

 

Java遍历List并删除某些元素

Java遍历List并删除某些元素

? 在阿里的Java编程规约中有一条:【强制】不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。

? 这里不禁要问为什么?

? 首先来试一下,遍历List时删除元素究竟行不行:

public class IteratorTest {

    public static void singleThreadRemove1(){
        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        for (String l : list){
            if ("1".equals(l)){
                list.remove(l);
            }
        }
        System.out.println(list);
    }

    public static void main(String[] args){
        singleThreadRemove1();
    }

}

运行结果:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
    at java.util.ArrayList$Itr.next(ArrayList.java:859)
    at blog.IteratorTest.singleThreadRemove1(IteratorTest.java:19)
    at blog.IteratorTest.main(IteratorTest.java:28)

Process finished with exit code 1

定位报错日志可以发现,遍历每个元素都会调用next()方法,next()方法首先有一个校验:

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

这里modCount是指list实际修改的次数,expectedModCount是预计修改的次数,调用List的remove()方法只会增加modCount的值,而不会增加expectedModCount,也就是说这是Java在让我们避免在for循环中删除list的元素。

? 要想知道为什么要避免这种操作很简单,将上面程序中条件改为“当元素值等于‘2’时删除元素”,此时会发现程序可以正常运行!当list遍历完第二个元素时,这时准备进入第三个元素,然而list的元素数量已经减少,第三个索引位置上没有元素,循环结束。这是多么荒唐的事啊,循环遍历元素,明明有一个元素没有走到,循环却正常结束。

以上是关于阿里巴巴为什么这样强制从List中删除元素的主要内容,如果未能解决你的问题,请参考以下文章

为什么阿里巴巴Java开发手册中强制要求不要在foreach循环里进行元素的remove和add操作?

为什么阿里巴巴强制不要在 foreach 里执行删除操作

Java中集合删除元素时候关于ConcurrentModificationException的迷惑点

从嵌套在 std::map 中的 std::list 中删除元素的最佳方法

怎样从Arraylist中删除一个元素

从列表的副本中删除时删除的列表元素[重复]