相关阅读
【小家java】java5新特性(简述十大新特性) 重要一跃
【小家java】java6新特性(简述十大新特性) 鸡肋升级
【小家java】java7新特性(简述八大新特性) 不温不火
【小家java】java8新特性(简述十大新特性) 饱受赞誉
【小家java】java9新特性(简述十大新特性) 褒贬不一
【小家java】java10新特性(简述十大新特性) 小步迭代
【小家java】java11新特性(简述八大新特性) 首个重磅LTS版本
每篇一句
一个男人最无能为力的就是,在他最没能力的年纪,遇见了他最想照顾一辈子的姑娘
1、概述
java中的集合框架是我们日常使用得最多的数据结构,而List作为Collection里最重要的一员,使用就更加的频繁了。因此我们平时使用中少不了对List的增删改查,本文就针对于对List的“删”操作进行一个分析,顺便说几个坑,希望能帮助到大家以后可以避免踩坑
2、栗子
有一个List,如果我们要删除其中的一个元素,怎么办呢? 这里我们先用remove方法
public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("aa"); list.add("bb"); list.add("cc"); System.out.println(list); //[aa, bb, cc] list.remove("aa"); System.out.println(list); //[bb, cc] }
public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("aa"); list.add("bb"); list.add("cc"); System.out.println(list); //[aa, bb, cc] list.remove("aa"); System.out.println(list); //[bb, cc] }
这个可能是我们使用得比较多的方式,直接remove即可。但,看下面例子,你能猜对结果吗?
public static void main(String[] args) { List<Integer> list = new ArrayList<>(); list.add(2); list.add(1); list.add(0); System.out.println(list); //[2, 1, 0] list.remove(2); System.out.println(list); //[2, 1] }
惊喜不?我们发现 我们得到的答案:发现2并没有被删除掉,而是把index为2的0元素的删除掉了。这是什么呢?原来,list里面有两个重载方法:
E remove(int index); //返回删除的元素 boolean remove(Object o); //返回bool值
显然,如果删除的元素不是整数,那是没有异议的。那如果删除的元素恰好就是整数呢?(如上面的例子),我们先来看看官方怎么说:
囧,查看了java官方文档,并没有找到相关的解释。(如果你有找到,请直接mark我哈)
不管了,看代码的执行效果是不会骗人的,如果你直接传数字,表示的是index,所以注意index一定不能out越界了哟。那么问题来了,如上例子,如果就想强制调用boolean remove(Object o)方法怎么办呢?官方虽然没有解释,但此处小编给一个答案给各位同学参考哈:
public static void main(String[] args) { List<Integer> list = new ArrayList<>(); list.add(2); list.add(1); list.add(0); System.out.println(list); //[2, 1, 0] Integer ele = 2; list.remove(ele); System.out.println(list); //[1, 0] }
我们会发现这样子,就是根据元素来删除的。或者这样子简写也是ok的
public static void main(String[] args) { List<Integer> list = new ArrayList<>(); list.add(2); list.add(1); list.add(0); System.out.println(list); //[2, 1, 0] list.remove((Integer)2); System.out.println(list); //[1, 0] }
我们发现,两个重载方法都是匹配的,那怎么就走那个了呢?因此为了给大家解惑,我这里简单介绍一下java的重载算法(或者说是匹配优先级):
1.先匹配参数个数
2.参数类型的最佳匹配:直接所属类(注意此处说的是最佳匹配)
3.如果没有找到直接所属类,会发生向上转型,直至找父类参数,直观上查找顺序为:包装类-》父类-》接口
4.如果向上转型仍无法匹配,则查找可变参数列表
5.以上无法匹配返回找不到方法错误(其实编译就会报错了)
显然我们发现,我们两个remove方法会在第三条匹配成功(注意:只会向上转型,而不会向下拆箱哟)。
天空一声巨响,主菜登场。。。
其实我们要删除List里面元素的时候,大多数情况下是进行循环删除。但上面两个删除是基础,因此下面介绍一下List循环删除的相关case,例如我现在有如下一个集合:
List<Integer> list = new ArrayList<>(); list.add(2); list.add(1); list.add(3); list.add(5); list.add(8); list.add(6); list.add(2); list.add(5); list.add(10);
备注:这里插一嘴,因为咱们要对list进行remove操作,所以此处对list的初始化,一定不能这么做:
List<Integer> list = Arrays.asList(2, 1, 3, 5, 8, 6, 2, 5, 10);
因为这么生成的list其实是Arrays自己实现的一个阉割版的List,它是木有实现remove方法的,所以无法实现删除操作。这里附上部分源码供参考:
//1、普通for循环遍历 Integer baseNum = 9; //以这个为基础 删除掉>=这个值的元素 System.out.println("剩余长度:" + list.size() + "---" + list); for (int i = 0; i < list.size(); i++) { if (list.get(i) >= baseNum) list.remove(i); } System.out.println("剩余长度:" + list.size() + "---" + list); 输出结果如下: 剩余长度:9---[2, 1, 3, 5, 8, 6, 2, 5, 9] 剩余长度:8---[2, 1, 3, 5, 8, 6, 2, 5]
表面上看,好像达到了我们的效果,是不是以后我们以后就可以用这种方法来删除了呢?如果你觉得ok,那就too young too simple AND sometimes native。咱们把baseNum调整成5再试一遍:
//1、普通for循环遍历 Integer baseNum = 5; //以这个为基础 删除掉>=这个值的元素 System.out.println("剩余长度:" + list.size() + "---" + list); for (int i = 0; i < list.size(); i++) { if (list.get(i) >= baseNum) list.remove(i); } System.out.println("剩余长度:" + list.size() + "---" + list); 输出结果如下: 剩余长度:9---[2, 1, 3, 5, 8, 6, 2, 5, 9] 剩余长度:6---[2, 1, 3, 8, 2, 9]
//1、普通for循环遍历 Integer baseNum = 5; //以这个为基础 删除掉>=这个值的元素 System.out.println("剩余长度:" + list.size() + "---" + list); for (int i = 0; i < list.size(); i++) { if (list.get(i) >= baseNum) list.remove(i); } System.out.println("剩余长度:" + list.size() + "---" + list); 输出结果如下: 剩余长度:9---[2, 1, 3, 5, 8, 6, 2, 5, 9] 剩余长度:6---[2, 1, 3, 8, 2, 9]
我们发现8和9元素竟然都还在没有被删除。所以,这种删除方式肯定是有问题的:问题在于,删除某个元素后,list的大小发生了变化,而你的索引也在变化,所以会导致你在遍历的时候漏掉某些元素(如上,8个9就被漏掉了)。按照我们的想法,我们用增强for循环?
//2、增强for循环 Integer baseNum = 5; //以这个为基础 删除掉>=这个值的元素 System.out.println("剩余长度:" + list.size() + "---" + list); for (Integer x : list) { if (x >= baseNum) list.remove(x); //java.util.ConcurrentModificationException } System.out.println("剩余长度:" + list.size() + "---" + list);
这种方式的问题在于,删除元素后继续循环会报错误信息ConcurrentModificationException,因为元素在使用的时候发生了并发的修改,导致异常抛出(其实这是很重要的fast-failed机制,后面博文会专门讨论这种机制的优点)。但是如果我们只需要删除一个元素,马上使用break跳出,则不会触发报错。