`
andy136566
  • 浏览: 290575 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

List remove操作容易出现的问题

阅读更多

import java.util.*;

  public class object {

  public static void main(String[] args) {

  String str1 = new String("abcde");

  String str2 = new String("abcde");

  String str3 = new String("abcde");

  String str4 = new String("abcde");

  String str5 = new String("abcde");

  List list = new ArrayList();

  list.add(str1);

  list.add(str2);

  list.add(str3);

  list.add(str4);

  list.add(str5);

  System.out.println("list.size()=" + list.size());

  for (int i = 0; i < list.size(); i++) {

  if (((String) list.get(i)).startsWith("abcde")) {

  list.remove(i);

  }

  }

  System.out.println("after remove:list.size()=" + list.size());

  }

  }

  运行结果不是:

  list.size()=5

  after remove:list.size()=0

  居然是:

  list.size()=5

  after remove:list.size()=2

  原因:List每remove掉一个元素以后,后面的元素都会向前移动,此时如果执行i=i+1,则刚刚移过来的元素没有被读取。

  解决方法:

  1.倒过来遍历list

  for (int i = list.size()-1; i > =0; i--) {

  if (((String) list.get(i)).startsWith("abcde")) {

  list.remove(i);

  }

  }

  2.每移除一个元素以后再把i移回来

  for (int i = 0; i < list.size(); i++) {

  if (((String) list.get(i)).startsWith("abcde")) {

  list.remove(i);

  i=i-1;

  }

  }

  3.使用iterator.remove()方法删除

  for (Iterator it = list.iterator(); it.hasNext();) {

  String str = (String)it.next();

  if (str.equals("chengang")){

  it.remove();

  }

  }

  注意:在遍历list或者说在遍历集合过程中,执行了删除动作就会报错

  工作中碰到个ConcurrentModificationException。代码如下:

  List list = ...;

  for(Iterator iter = list.iterator(); iter.hasNext();) {

  Object obj = iter.next();

  ...

  if(***) {

  list.remove(obj);

  }

  }

  在执行了remove方法之后,再去执行循环,iter.next()的时候,报java.util.ConcurrentModificationException(当然,如果remove的是最后一条,就不会再去执行next()操作了)

  下面来看一下源码

  public interface Iterator<E> {

  boolean hasNext();

  E next();

  void remove();

  }

  public interface Collection<E> extends Iterable<E> {

  ...

  Iterator<E> iterator();

  boolean add(E o);

  boolean remove(Object o);

  ...

  }

  这里有两个remove方法

  接下来来看看AbstractList

  public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {

  //AbstractCollection和List都继承了Collection

  protected transient int modCount = 0;

  private class Itr implements Iterator<E> { //内部类Itr

  int cursor = 0;

  int lastRet = -1;

  int expectedModCount = modCount;

  public boolean hasNext() {

  return cursor != size();

  }

  public E next() {

  checkForComodification(); //特别注意这个方法

  try {

  E next = get(cursor);

  lastRet = cursor++;

  return next;

  } catch(IndexOutOfBoundsException e) {

  checkForComodification();

  throw new NoSuchElementException();

  }

  }

  public void remove() {

  if (lastRet == -1)

  throw new IllegalStateException();

  checkForComodification();

  try {

  AbstractList.this.remove(lastRet); //执行remove对象的操作

  if (lastRet < cursor)

  cursor--;

  lastRet = -1;

  expectedModCount = modCount; //重新设置了expectedModCount的值,避免了ConcurrentModificationException的产生

  } catch(IndexOutOfBoundsException e) {

  throw new ConcurrentModificationException();

  }

  }

  final void checkForComodification() {

  if (modCount != expectedModCount) //当expectedModCount和modCount不相等时,就抛出ConcurrentModificationException

  throw new ConcurrentModificationException();

  }

  }

  }

  remove(Object o)在ArrayList中实现如下:

  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 < size; index++)

  if (o.equals(elementData[index])) {

  fastRemove(index);

  return true;

  }

  }

  return false;

  }

  private void fastRemove(int index) {

  modCount++; //只增加了modCount

  ....

  }

  所以,产生ConcurrentModificationException的原因就是:

  执行remove(Object o)方法之后,modCount和expectedModCount不相等了。然后当代码执行到next()方法时,判断了checkForComodification(),发现两个数值不等,就抛出了该Exception。

  要避免这个Exception,就应该使用remove()方法。

  这里我们就不看add(Object o)方法了,也是同样的原因,但没有对应的add()方法。一般嘛,就另建一个List了

  下面是网上的其他解释,更能从本质上解释原因:

  Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。

  所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

 

 

 

[转]http://www.qqread.com/java/2010/08/y495083.html

分享到:
评论

相关推荐

    前端项目-classlist.zip

    在不支持`classList`属性的老版本浏览器中,开发者通常需要使用字符串操作方法(如`className`的getter和setter)来管理类名,这种方式不仅繁琐且容易出错。`classList.js-master`项目就是为了解决这个问题,它提供...

    标准C++的StringList

    在标准C++中,`StringList`的概念是对VCL(Visual ...这样的类设计允许用户方便地创建、操作和管理字符串列表,同时保持了与VCL中`TStringList`接口的相似性,使得从Delphi或其他VCL环境转到C++的开发者更容易适应。

    list集合案例增、删、改、查,ArrayList与LinkedList的区别,LinkedList堆栈/队列的开发

    2. 删除(Remove):`remove(int index)`方法用于移除指定索引处的元素,而`remove(Object o)`方法则用于移除列表中首次出现的指定对象。 3. 修改(Modify):`set(int index, E element)`方法允许我们替换列表中...

    list,set,map,数组间的相互转换

    数组可以直接转换为`List`,但是需要注意的是,通过`Arrays.asList()`方法创建的`List`实际上是一个不可变的列表,也就是说它不支持`add()`或`remove()`等操作。如果需要一个可修改的`List`,可以通过以下方式实现:...

    stl 解决约瑟夫问题

    `std::list`不提供随机访问,但是其元素迭代器在删除操作后仍然有效,这使得我们可以在循环过程中更容易地进行操作。首先创建一个`std::list`,将所有人加入。然后,同样设置一个计数器,当计数达到指定值时,使用`...

    List实现 数据结构 模板

    双向链表每个节点不仅有指向下一个节点的指针,还有一个指向前一个节点的指针,这使得向前和向后遍历都变得容易。在C++中,我们可以扩展`Node`结构体,添加一个指向前一个节点的指针,并相应地更新`LinkedList`类: ...

    Java程序员容易犯的10个错误

    正确做法是使用迭代器,但使用增强for循环(for-each)配合`list.remove()`会抛出`ConcurrentModificationException`。正确的迭代器移除方式如下: ```java Iterator&lt;String&gt; iter = list.iterator(); while ...

    Python中list循环遍历删除数据的正确方法

    然而,在遍历list的过程中删除数据是一个需要特别注意的操作,因为不当的处理很容易造成程序出错,例如IndexError: list index out of range异常。 在初学Python时,可能会遇到这样的问题:当我们试图在遍历list的...

    避坑手册 - JAVA编码中容易踩坑的十大陷阱.doc

    以下是一些在Java编码中容易出现的陷阱及其解决方法: 1. 循环中操作目标List: 当你需要在遍历List的同时删除元素时,直接使用for-each循环或者索引循环是错误的。这两种方式都会导致`...

    基于List, Set, Map, Integer, String, Tuple, Deque模块的C++库

    但是,为了达到Python的易用性,我们需要添加一些便捷的成员函数,比如`append`, `extend`, `insert`, `remove`, `pop`等,让开发者能够更直观地操作列表。 Set在C++中对应的类型是`std::set`,这是一个红黑树实现...

    C#中数组、ArrayList和List三者的区别详解及实例

    - **优点**:在声明List时需要指定元素类型T,这使得List成为类型安全的,避免了类型转换异常和不必要的装箱拆箱操作。 ```csharp List&lt;string&gt; list = new List(); list.Add("abc"); // 添加元素 list[0] = ...

    dom4j操作xml

    它提供了简单且直观的API,使得XML的解析、创建、查询和修改变得容易。在本文中,我们将深入探讨DOM4J如何进行XML操作,包括增加、删除和修改XML元素。 首先,我们需要理解XML的基本结构。XML(可扩展标记语言)是...

    操作系统课程算法设计

    remove_from_list(p); } else { adjust_partition(p, size); // 调整分区 } merge_adjacent_areas(); // 合并相邻空闲区 } ``` --- #### 设计2:哲学家就餐问题与死锁 **1. 哲学家就餐问题** - **问题描述*...

    数组的 用法

    列表提供了许多方便的方法,如Add、Remove、IndexOf、Insert等,使操作更灵活。例如,如果你想在列表末尾添加一个元素,可以使用Add方法;如果想删除某个元素,可以使用Remove方法。 数组和List在性能和使用场景上...

    c++ 模板 双向链表 操作

    双向链表是一种高级数据结构,它不仅在每个节点中存储数据,还包含指向前一个节点和后一个节点的指针,使得在链表中的前后移动变得容易。在本篇文章中,我们将深入探讨如何使用C++模板来实现双向链表及其操作,如...

    Java平台提供了一个全新的集合框架.doc

    它提供了添加(add)、删除(remove)以及查询(size、isEmpty、contains)等基本操作。通过iterator()方法,可以获取一个迭代器,以便按顺序访问集合中的元素。Collection接口还包含了针对元素组的操作,如contains...

    java程序中容易出错的地方

    ### Java程序中容易出错的地方 在Java编程中,即便是经验丰富的开发人员也难免会遇到一些常见的陷阱和误区。本文将深入探讨这些易错点,帮助开发者更好地理解这些问题,并学会如何避免它们。 #### 1. 类加载问题 ...

    leetcode走方格起点到终点-LC-solutions:慢慢来

    容易出现的问题就是: for x in nums: 我们通常尽量避免修改一个正在进行遍历的列表,可以使用切片操作克隆这个list来避免这个问题(浅拷贝) #26 class Solution: def removeDuplicates(self, nums: List[int]) -&gt; ...

    js自动校验之动态生成错误提示信息节点(复习下如何操作dom节点)

    在JavaScript中,我们可以直接修改元素的`style`属性,或者通过`classList.add()`和`classList.remove()`方法切换类名,以应用预定义的CSS样式。 标签"源码"意味着博客可能会提供示例代码,帮助读者理解如何实现...

    设计模式之:组合模式

    总结起来,**组合模式**是解决树形结构问题的有效工具,它简化了客户端对部分-整体层次结构的处理,增强了代码的可读性和可维护性。通过统一接口,我们可以灵活地添加、删除和操作对象,无论是单个对象还是整个组合...

Global site tag (gtag.js) - Google Analytics