关于List比较好玩的操作

       作为Java大家庭中的集合类框架,List应该是平时开发中最常用的,可能有这种需求,当集合中的某些元素符合一定条件时,想要删除这个元素。如:

public class ListTest {
    public static void main(String[] args) {
       List<Integer> intList = new ArrayList<Integer>();
	   Collections.addAll(intList, 1, 2, 3, 5, 6);
       // for循环优化写法,只获取一次长度
       for(int i = 0, size = intList.size(); i < size; i++) {
           Integer value = intList.get(i);
           // 符合条件,删除元素
           if(value == 3 || value == 5) {
              intList.remove(i);
           }
       }
       System.out.println(intList);
    }
}

      执行后,会抛出IndexOutOfBoundsException,因为集合中存在符合条件的元素,删除后,集合长度动态改变,由于长度只获取一次,发生越界,所以,去掉for循环优化,如:

public class ListTest {
    public static void main(String[] args) {
       List<Integer> intList = new ArrayList<Integer>();
	   Collections.addAll(intList, 1, 2, 3, 5, 6);
       for(int i = 0; i < intList.size(); i++) {
           Integer value = intList.get(i);
           // 符合条件,删除元素
           if(value == 3 || value == 5) {
              intList.remove(i);
           }
       }
       System.out.println(intList);
    }
}

      输出:[1, 2, 5, 6],漏掉了5这个元素,当i=2的时候,值为3,删除后,后面的元素往前补一位,这时i=3的时候,值为6,跳过了5,这样也不行,随后想到了用for循环增强,不显示的操作下标,直接操作对象,如:

public class ListTest {
    public static void main(String[] args) {
       List<Integer> intList = new ArrayList<Integer>();
	   Collections.addAll(intList, 1, 2, 3, 5, 6);
       for(Integer value : intList) {
           // 符合条件,删除元素
           if(value == 3 || value == 5) {
              intList.remove(value);
           }
       }
       System.out.println(intList);
    }
}

      执行后,会抛出ConcurrentModificationException,字面意思是并发修改异常。异常跟踪信息如下:

Exception inthread "main" java.util.ConcurrentModificationException

         atjava.util.AbstractList$Itr.checkForComodification(AbstractList.java:449)

         at java.util.AbstractList$Itr.next(AbstractList.java:420)

         at ListTest.main(ListTest.java:13)

       可以大概看出是执行到AbstractList中内部类Itr的checkForComodification方法抛出的异常,至于为什么出现异常,这里可以大概解释一下。集合遍历是使用Iterator, Iterator是工作在一个独立的线程中,并且拥有一个互斥锁。Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast原则 Iterator 会马上抛出java.util.ConcurrentModificationException 异常。所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。

       而要解决这个问题,可以使用Iterator的remove方法,该方法会删除当前迭代对象的同时,维护索引的一致性。如:

public class ListTest {
    public static void main(String[] args) {
       List<Integer> intList = new ArrayList<Integer>();
	   Collections.addAll(intList, 1, 2, 3, 5, 6);
       Iterator<Integer> it = intList.iterator();
       while(it.hasNext()) {
           Integer value = it.next();
           if(value == 3 || value == 5) {
              it.remove();
           }
       }
       System.out.println(intList);
    }
}

      输出正确结果:[1, 2, 6]。

       不使用迭代器的解决方案就是,自己维护索引,删除一个元素后,索引-1,如:

public class ListTest {
    public static void main(String[] args) {
       List<Integer> intList = new ArrayList<Integer>();
	   Collections.addAll(intList, 1, 2, 3, 5, 6);
       for(int i = 0; i < intList.size(); i++) {
           Integer value = intList.get(i);
           if(value == 3 || value == 5) {
              intList.remove(i);
              i--;
           }
       }
        System.out.println(intList);
    }
}

      输出正确结果:[1, 2, 6]。

       还有种取巧的方式是从最后一个元素开始遍历,符合条件的删除,如:

public class ListTest {
    public static void main(String[] args) {
       List<Integer> intList = new ArrayList<Integer>();
	   Collections.addAll(intList, 1, 2, 3, 5, 6);
       for(int i = intList.size() - 1; i >= 0; i--) {
           Integer value = intList.get(i);
           if(value == 3 || value == 5) {
              intList.remove(i);
           }
       }
        System.out.println(intList);
    }
}

      输出正确结果:[1, 2, 6]。

       最后,Java集合类框架真是大大方便了开发,不用自己去维护数组,随时担心着越界等问题。当然List的实现类对插入、删除的效率不太一样,这取决于其实现的数据结构,是选择删除,还是选择新建个集合,这里就不做讨论了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏电光石火

HashSet/HashMap详解

HashMap和HashSet是Java Collection接口两个重要的成员,其中HashMap是Map接口常用的实现类,HashSet是Set接口常用...

21210
来自专栏Java帮帮-微信公众号-技术文章全总结

HashSet 源码分析【面试+工作】

在工作中,经常有这样的需求,需要判断某个ID是否在某个组的管理之下等,就需要查询该组下的ID放到一个集合中,且集合中元素不能有重复,之后判断该集合是否包含我们的...

763
来自专栏赵俊的Java专栏

LeetCode 804 Unique Morse Code Words

首先为每个单词的每个字符进行转码, 将转码后的数据放到 Set 集合中, 最后返回 Set 的长度。

854
来自专栏C/C++基础

树堆(Treap)图文详解与实现

树堆(Treap)是二叉排序树(Binary Sort Tree)与堆(Heap)结合产生的一种拥有堆性质的二叉排序树。

1161
来自专栏用户3030674的专栏

Java 工具类—日期获得,随机数,系统命令,数据类型转换

1071
来自专栏Java技术栈

Java List面试题汇总

1、你知道的List都有哪些? 2、List和Vector有什么区别? 3、List是有序的吗? 4、ArrayList和LinkedList的区别?分别用在什...

3589
来自专栏后端之路

优先级队列之PriorityQueue

背景 前面几篇的队列基本都是和插入顺序相关的,一般来说可以先进先出,当然也可以通过双向队列实现进后出等等 那么在开发中还存在如下一些需求 不同的任务过来可以进入...

2967
来自专栏IMWeb前端团队

ES6 Set

本文作者:IMWeb kurtshen 原文出处:IMWeb社区 未经同意,禁止转载 ES6 Set ES6 新增了几种集合类型,本文主要介绍Set以...

1777
来自专栏用户画像

6.2.2 折半查找

折半查找,又称二分查找,它适用于有序的顺序表。基本思路是:首先将给定值key与表中中间位置元素的关键字比较,若相等,则查找成功,返回该元素的存储位置;若不等,则...

521
来自专栏desperate633

HashSet实现原理分析(Java源码剖析)add(E e)remove(Object o)iterator()小结

本文将深入讨论HashSet实现原理的源码细节。在分析源码之前,首先我们需要对HashSet有一个基本的理解。

1723

扫码关注云+社区