Java ArrayList Remove方法失效的问题
看到一篇博客:
我们经常会使用的方法删除元素,看起来是很简单的调用,但是真的是机关重重。
1. 删除jdk中的类对象
我们先来创建一个数组列表
ArrayList array = new ArrayList();
array.add(2);
array.add(2);
array.add(1);
array.add(1);
array.add(3);
array.add(3);
当遍历这个数组列表,可以依次打印2,2,1,1,3,3。好,现在要完成一个功能,删除这个array里面所有为1的元素。思路如下:for循环遍历数组列表,如果元素为1,就掉
– 使用for循环遍历
public void remove(ArrayList list) {
Integer in = 1;
for (int i = 0; i < list.size(); i++) {
Integer s = list.get(i);
if ( s.equals(in) )
{
list.remove(s);
}
}
}
看上去我们的代码很完美,在理论上肯定会达到预期的结果,但是当遍历删除后的array,会发现,打印结果为2,2,1,3,3。怎么还有一个1没删除呢,现在我们来看在这段代码中用到的方法
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
}
先不用管方法中的逻辑是怎么样的,看代码可以发现,最终删除元素的操作是这个方法执行的。具体来看中的代码,它把要删除的那个元素之后的所有元素都往前移了一个位置,然后把最后空出来的位置设置为null,删除的原理是直接覆盖被删除元素的空间位置。具体解释一下:一开始数组列表是2,2,1,1,3,3, 当循环到下标为2,也就是第一个“1”元素出现时,执行,而具体删除元素的操作是执行的,会将第一个“1”元素后面所有的元素都往前移一位,这样第一次后的数组就变成了2,2,1,3,3。第二个“1”元素代替了原先第一个“1”的位置,也就是下标为2的位置,但是我们下一次循环比较的是下标为3的元素,如果不往前移,就是比较第二个“1”,不会出现任何问题。现在这个“1”往前移了,就会比较“3”元素Java ArrayList Remove方法失效的问题,正好漏掉了第二个“1”,所以会出现那样的结果。这样看来上面的代码还有一个注意点,就是for循环的条件,既然数组列表的size是会变的,那我们就不能这样写,不然会抛出ion
int n = list.size();
for (int i = 0; i < n; i++)
现在我们把数组列表里的元素换一换,换成2,2,1,1,3,3,1。还是执行以上的代码,会发现删除后的结果为:2,2,3,3,1。这很奇怪,如果按照上面的逻辑,当第一个“1”被删除之后,第二个“1”往前移,应该漏掉的是第二个“1“啊,怎么反而第二个“1”没漏掉,第三个漏掉了。现在我们仔细看一下方法,看else部分,当我们把要删除的元素以参数的形式传进来之后,方法内部居然又做了一次循环。看代码可以知道这个循环的功能是删除数组中首次出现的与参数相同的元素。这就解释了为什么在循环到最后一个“1”时删除的却是第二个“1”了。
我们将上面的删除代码稍微修改一下,将list.(s);改为list.(i);
public void remove(ArrayList list) {
Integer in = 1;
for (int i = 0; i < list.size(); i++)
{
Integer s = list.get(i);
if ( s.equals(in) )
{
list.remove(i);
}
}
}
原来的代码是根据具体的对象来删除元素,现在改为根据下标来删除元素。可以这么改,是因为提供了两个方法,( o)和(int index)。现在来看一下执行的结果,初始的数组元素还是2,2,1,1,3,3,1。删除后的数组为2,2,1,3,3。两个方法执行的结果却是不一样的,好神奇。来看一下这个方法的源码
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
return oldValue;
}

原来这个方法不是删除首次出现的指定元素,而是下标传进来是什么,就删除那个下标的元素,但还是会出现有元素漏删的情况。先总结一下两个方法的功能,再讨论一下如何去避免元素漏删。
这里我想插几句,嘿嘿嘿
int a = 1;
remove(a);
Integer b = 1;
remove(b);
这两次调用到底访问的是哪个方法呢,第一次会不会a自动装箱成类型,访问( o)呢?第二次会不会b自动拆箱成int类型,访问(int index)呢?
答案肯定是否定的,编译器在编译的时候,发现执行(a),有相应的方法与之匹配,就不会进行自动装箱这个操作了。(自动拆装箱发生在编译期)
可以看一下我另一篇文章 使用Java自动拆装箱出现的一些意外和原理
言归正传,怎么去避免元素漏删的问题呢?既然删除后的元素都是往前面移动位置,那么我们可以倒序遍历列表
public void remove2(ArrayList list)
{
Integer in = 1;
for (int i = list.size() - 1; i >= 0; i--)
{
Integer s = list.get(i);
if ( s.equals(in) )
{
list.remove(i);
}
}
}
这里我使用的方法是(int index),因为我在自定义的方法里已经通过for循环找到要删除的元素是哪个了,那就根据下标直接删除好了,另一个方法还要在内部再次for循环,就显得多此一举了,还影响性能。
有些人不喜欢用for循环,喜欢用,那么我们用再次上演上面的错误
– 使用
public void remove3(ArrayList list)
{
Integer in = 1;
for (Integer i : list)
{
if ( i.equals(in) )
{
list.remove(i);
}
}
}
运行发现,这玩意居然报错了java.util.。
先看一下反编译后的代码
Integer integer = Integer.valueOf(1);
Iterator iterator = arraylist.iterator();
do
{
if(!iterator.hasNext())
break;
Integer integer1 = (Integer)iterator.next();
if(integer1.equals(integer))
arraylist.remove(integer1);
} while(true);
其实是根据list对象创建一个对象,用这个迭代对象去遍历列表,如果要对list进行增删操作,都是要经过的。有两个重要的方法,判断是否有下一个元素,next返回下一个元素。看来问题出在next代码上,看看的父类是怎么实现这个方法的
public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

终于找到异常抛出的地方了(on),next方法会先检查和是否相等。表示list对象从new出来之后到现在被修改的次数,使用add和方法,这个值都会相应的增加和减少;指的是现在期望list被修改的次数是多少次。
原来的数组列表是2,2,1,1,3,3,1,和相等java中remove方法,当删除了第一个“1”元素时,会自动减一Java ArrayList Remove方法失效的问题,当进行下一次next时,和肯定就不相等了,也就会报错。
但是也是会有不报错的时候的java中remove方法,当删除的是倒数第二个元素,且这个元素在这个列表中是第一次出现,例如:2,2,1,1,3,3,4,1要删除4这个元素。为什么会出现这种情况呢,因为删了4之后,就不会再去检查4后面的1了,看代码,此时和size()正好相等。即使这没有报错,但还是会把最后一个元素漏删,如果数组是2,2,1,1,3,3,4,4,最后一个4就被漏删了
int cursor = 0;
public boolean hasNext() {
return cursor != size();
}
具体cursor如何增加可以看next方法
所以java中remove方法,要使用的方法时就不要用
还是使用中的方法去代替中的吧
public void remove4(ArrayList list)
{
Integer in = 1;
Iterator it = list.iterator();
while (it.hasNext())
{
Integer s = it.next();
if (s.equals(in))
{
it.remove();
}
}
}
到这里,你以为的里的陷阱已经被挖透了么,这还远远没有,以上都是jdk自带的类。现在来试试自定义的类对象
2. 删除自定义的类对象
class Student{
private String name;
private int id;
public Student(String name, int id) {
this.name = name;
this.id = id;
}
@Override
public String toString() {
return "Student [name=" + name + ", id=" + id + "]";
}
}
ArrayList array = new ArrayList();
array.add(new Student("zhang1",1));
array.add(new Student("zhang2",2));
array.remove(new Student("zhang1",1));
打印结果,会发现对象并没有被删掉,看方法中有一行代码是这样的,if (o.()) 这里的调用的是类中的方法,而中的比较的是二者的引用,那就肯定删不掉了,这时需要重写方法(也是重写了方法)
class Student{
private String name;
private int id;
public Student(String name, int id) {
this.name = name;
this.id = id;
}
@Override
public String toString() {
return "Student [name=" + name + ", id=" + id + "]";
}
@Override
public boolean equals(Object otherObject){
if(this == otherObject){return true;}
if(otherObject == null){return false;}
if(getClass() != otherObject.getClass()){return false;}
Student other = (Student)otherObject;
return Objects.equals(name,other.name) && id == other.id ;
}
}
结果发现,id为1,name为的学生被删掉了
总结:的 方法要么重写方法,要么使用方法;
方法参考:
重写方法参考:
1. 本站所有资源来源于用户上传和网络,如有侵权请联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系站长处理!
6. 本站不售卖代码,资源标价只是站长收集整理的辛苦费!如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。
7. 站长QQ号码 2205675299
资源库 - 资源分享下载网 » Java ArrayList Remove方法失效的问题
常见问题FAQ
- 关于资源售价和售后服务的说明?
- 代码有没有售后服务和技术支持?
- 有没有搭建服务?
- 链接地址失效了怎么办?
- 关于解压密码

