本文版权归作者所有,仅供用来网上学习来用,读者可以收藏,请不要下载到本机或重新发布到其它网站
先看一个例子:
import java.util.ArrayList;
import java.util.List;
public class CollectionOperation {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10; i++) {
list.add(String.valueOf(i));
}
for (String str : list) {
if (str.equals("5")) {
list.remove(str);
}
}
}
}
这个程序可以通过编译,但在运行时会出现异常,异常信息如下:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at CollectionOperation.main(CollectionOperation.java:12)
为什么会出现这种异常呢?因为在执行到第14行时,list的长度发生了变化,由原来的10变为9。第12行的for (String integer : list)这种循环是在JDK5以后才支持的。而这种循环对于集合类来说,会生成一个迭代对象Iterator,然后进行遍历的。在每次循环之前,它会检查集合类当前的长度是否发了变化,如果发生变化就会抛出如上的异常。对于ArrayList, 这段检查的代码是这样的(此段代码是写在AbstractList类的内部类Itr中):
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
因此,这种JDK5新规范的循环的写法,虽然简单,但是如果有对集合类对象有添加和删除的操作时,会出现这种异常。这时需要用常归的循环的写法了。
import java.util.ArrayList;
import java.util.List;
public class CollectionOperation {
public static void main1(String[] args) {
int size = 10;
List<String> list = new ArrayList<String>();
for (int i = 0; i < size; i++) {
list.add(String.valueOf(i));
}
for (int i = 0; i < size; i++) {
String str = list.get(i);
if (str.equals("5")) {
list.remove(str);
}
}
}
}
我们写程序一般很少在遍历的时候添加元素,多数是在遍历的时候,找到匹配的元素,然后进行删除操作。就像上面的例子一样,但是细心的朋友会发现,上面的例子有隐患存在。重新更改一下上面的例子,这个隐患就会暴露出来。
import java.util.ArrayList;
import java.util.List;
public class CollectionOperation {
public static void main1(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10; i++) {
list.add(String.valueOf(5));
}
for (int i = 0; i < list.size(); i++) {
String str = list.get(i);
if (str.equals("5")) {
list.remove(str);
}
}
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
只更改了一下第10行的值为5,也就是List中存放的都是5,对于后面的删除操作,我们本来是打算删除列表中值等于5的元素,即删除List中的全部元素,运行一下程序,我们发现,并没有完全删除,而是剩下了5个元素。
再看一个更奇怪的例子,还是这样的循环,我们删除一个数列上偶数位上的数,只留奇数位上的数。例如:原数列为10、20、30、40、50、60、70、80、90、100,则偶数位上的数为20、40、60、80、100,删除后剩余的数为:10、30、50、70、90。一般人会写出下面的错误程序:
public class CollectionOperation {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 10; i++) {
list.add((i + 1) * 10);
}
for (int i = 0; i < list.size(); i++) {
if ((i + 1) % 2 == 0) {
list.remove(i);
}
}
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i) +" ");
}
}
}
猛一看这个程序,是对的,可是一运行出来结果就不对。这个程序的结果是:
10 30 40 60 70 90 100
这个结果并不是我们想要的,但是到底哪里出错了?因为是因为集合类List为可变长的数组,当删除中间一个元素后,这个被删除元素的后面的元素会向前移动一个,即元素的索引变小了,但是循环变量i是没有变化的,这时再向后遍历时,会漏掉刚删除那个元素上新移进的元素。这样一直往复,直到结束。
怎样解决这个问题呢?
改换一下思路,因为删除元素的时候,后面的元素都是向前移动,因此,我们这样循环:
for (int i = list.size(); i >= 0; i--) {
if ((i + 1) % 2 == 0) {
list.remove(i);
}
}
再试一下,结果正确了:
10 30 50 70 90
这种循环很常见,也很有效果,且效率很高。因为List的size方法只被调用了一次。在JDK的原码中,很多listener的遍历都是用的倒序。
分享到:
相关推荐
8. **集合的补集**:获取第一个集合中不包含在第二个集合的所有元素,这需要先创建一个全集,然后用差集操作来实现。 ```java Set<Integer> universal = new HashSet(set1); universal.addAll(set2); Set<Integer> ...
本文将详细讲解C++编程中集合类的实现,包括集合类的定义、添加元素、删除元素、判断集合是否为空或满、判断元素是否包含在集合中、计算集合中的元素总数、输出集合中的元素、求两个集合的交集和并集等操作。...
本文将结合上述 Bloch 关于线程安全等级的定义,对 Java 集合框架中的集合类进行线程安全性分析,并指出各个集合类在现实的编程环境中需要注意的并发编程的陷阱;同时对集合框架中通用算法对线程安全性的影响进行...
在Java编程中,集合框架是不可或缺的一部分,它提供了一种高效、灵活的方式来存储和操作对象。自定义集合类则是开发者根据特定需求扩展Java集合框架的行为,以满足个性化或特定业务场景的功能需求。以下是对"java...
本文实例讲述了C#集合遍历时删除和增加元素的方法。分享给大家供大家参考,具体如下: 大多数时候,遍历集合元素的时候并不需要对元素进行增加或者删除操作,但有些时候则需要,比如,如果集合中盛放的元素是社会上...
在使用集合类时,我们还需要注意一些其他关键接口,如`Map`接口,它表示键值对的集合,常见的实现有`HashMap`和`TreeMap`等。`Map`接口不继承自`Collection`,因为它不直接包含元素,而是包含键值对。 此外,...
在IT行业中,集合类是数据结构的一个重要组成部分,它们提供了组织和操作数据的方便接口。在C#编程语言中,标准库提供了丰富的集合类,如List、Dictionary, TValue>等。然而,有时我们需要处理一个键可以对应多个值...
Java集合类是Java语言中用来存储...整个集合框架非常庞大,本文仅就部分概念和类进行了详细说明,实际上,不同的集合类在性能、线程安全、存储结构等方面都有所不同,开发者应该根据实际需求选择合适的集合类来使用。
在Java编程语言中,集合框架是处理对象组的重要工具,它提供了一种高效且灵活的方式来存储和操作...在实际编程中,我们需要根据具体需求选择合适的集合类,并合理利用工具类提供的功能,以达到最佳的性能和可维护性。
在C++编程中,集合类是STL(标准模板库)的一部分,用于高效地管理和操作数据集合。在微软的MFC(Microsoft Foundation Classes)库中,也提供了集合类的实现,便于开发者处理对象的集合。"Cpp集合类.rar"这个压缩包...
实现Comparable接口或Comparator接口,用户可以根据需要对集合中的元素进行排序。为了方便用户使用,Java平台还提供了Collections和Arrays工具类。collection.rar分别对上述内容进行详细讲解演示。
在C#编程中,集合类是用于存储和管理对象的关键组件。C#的集合类主要分布在几个不同的命名空间中,包括System.Collections、System.Collections.Generic和System.Collections.Specialized。这些命名空间提供了不同...
在C++编程中,集合类是一种常见的数据结构,它提供了对一组特定对象的管理功能,如添加、删除、查找和操作这些对象。本程序通过数组实现集合类,这是对基本数据结构的一种简单而直观的实现方式。下面我们将深入探讨...
在Java中,集合主要分为两大类:Collection和Map。本文将对Java集合框架中的Collection接口及其相关接口、类进行深入的探讨。 首先,Collection接口是所有单值容器的基础,它是Set、List和Queue接口的父接口。...
开发者可以根据需求选择最适合的集合类型,比如在需要高效查找的场景下选择ArrayList,而在处理大量插入和删除操作时选择LinkedList。此外,Set接口用于存储无序且不重复的元素,适合去重或表示一组独特的对象。Map...
- 集合类能够根据需要动态扩展其大小。 - 集合类支持多种不同的存储方式和访问策略。 **集合与数组的区别**: - 数组是定长的,而集合是可变长度的。 - 数组既可以包含基本数据类型也可以包含对象,而集合只包含...
在实际编程中,选择合适的集合类和操作方式对于程序的性能至关重要。比如,如果需要频繁进行元素的添加和删除,`LinkedList`可能是更好的选择;如果对元素的顺序有特定要求,`ArrayList`或`TreeSet`可能更适合;如果...
- 定义泛型类`Purchase<T>`,在类中创建一个`Set`集合。 - 实现`insertGoods()`方法,用于添加商品到购物车。 - 实现`checkGoods()`方法,用于查看购物车中的商品。 - 示例中,添加了一款名为“保时捷手机”的...
在C++编程中,集合类(Set)是一种用于存储唯一对象的数据结构,它不包含重复元素。在本项目中,我们关注的是一个基于数组实现的简单整数集合,提供了诸如交集、并集等基本操作的菜单界面。下面将详细讨论相关知识点...
在选择集合类时,需要考虑到存储数据的特点和需求。如果需要存储大量数据,可以选择 ArrayList 或 LinkedList。如果需要存储键值对的数据,可以选择 HashMap 或 TreeMap。如果需要存储唯一的数据,可以选择 HashSet ...