IT培训网 - IT职场人学IT技术上IT培训网
Java list集合遍历操作遇到的问题
时间:2018-03-01 14:31:51 来源:Java培训网 作者:IT培训网 已有:名学员访问该课程
Java程序员在使用java list集合遍历操作时经常会遇到一些问题,遇到问题该如何解决,想必一些新手也是非常极手,在此,小编给大家汇总了一些java list集合遍历操作常见的问题,给大家做详细分析。
Java list集合遍历操作常见的问题
1、下面四个方法有什么问题吗?为什么?
public void remove1(ArrayList<Integer> list) {
for(Integer a : list){
if(a <= 10) {
list.remove(a);
}
}
}
public void remove2(ArrayList<Integer> list) {
Iterator<Integer> it = list.iterator();
while(it.hasNext()) {
if(it.next() <= 10) {
it.remove();
}
}
}
public void remove3(ArrayList<Integer> list) {
Iterator<Integer> it = list.iterator();
while(it.hasNext()) {
it.remove();
}
}
public void remove4(ArrayList<Integer> list) {
Iterator<Integer> it = list.iterator();
while(it.hasNext()) {
it.next();
it.remove();
it.remove();
}
}
答:这个问题很小儿科,答案如下。
remove1 方法会抛出 ConcurrentModificationException 异常,这是迭代器的一个陷阱,foreach 遍历编译后实质会替换为迭代器实现(普通for循环不会抛这个异常,因为list.size方法一般不会变,所以只会漏删除),因为迭代器内部会维护一些索引位置数据,要求在迭代过程中容器不能发生结构性变化(添加、插入、删除,修改数据不算),否则这些索引位置数据就失效了,避免的方式就是使用迭代器的 remove 方法。
remove2 方法可以正常运行,无任何错误。
remove3 方法会抛出 IllegalStateException 异常,因为使用迭代器的 remove 方法前必须先调用 next 方法,next 方法会检测容器是否发生了结构性变化,然后更新 cursor 和 lastRet 值,直接不调用 next 而 remove 会导致相关值不正确。
remove4 方法会抛出 IllegalStateException 异常,理由同 remove3,remove 调用一次后 lastRet 值会重置为 -1,没有调用 next 去设置 lastRet 的情况下再直接调一次 remove 自然就状态异常了。
2、下面程序片段的 remove 方法想删除 list 列表中的所有 "android",请分别说说其各个 remove 方法有没有问题?为什么?该怎么解决?
ArrayList<String> list = new ArrayList<String>();
list.add("java");
list.add("android");
list.add("android");
list.add("c");
list.add("c++");
list.add("c");
public void remove1(ArrayList<String> list) {
for (inti=0; i<list.size(); i++) {
String s = list.get(i);
if (s.equals("android")) {
list.remove(s);
}
}
}
public void remove2(ArrayList<String> list) {
for (Strings: list) {
if (s.equals("android")) {
list.remove(s);
}
}
}
答:上面程序的 remove1 方法和 remove2 方法运行情况如下。
remove1 方法执行后第二个 “android” 字符串没有被删掉,因为我们可以看下 ArrayList 的 remove 方法(ArrayList 有两个同名不同参的 remove 方法)实现核心代码:
public boolean remove(Object o) {
if (o == null) {
......
} 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++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
可以看到最终删除会执行 System.arraycopy 方法而导致删除元素时涉及到数组元素的移动,所以在遍历第一个字符串 "android" 时因为符合删除条件,所以将该元素从数组中删除,并且将后一个元素移动(也就是第二个字符串 "android")至当前位置,导致下一次循环遍历时后一个字符串 "android" 并没有被遍历到,所以无法删除,同时由于每删除一次 size 也减一,所以其实每次都会向前移位,也会导致越来越多的元素无法被遍历获取。解决的办法就是倒序遍历删除(千万不要认为将 for 循环里 list.size() 提到外边先变量取值就可以删除,这样会报列表索引越界),这种方法的效率相对好些,如下:
public void remove1(ArrayList<String> list) {
for(int i=list.size()-1; i>=0; i--) {
String s = list.get(i);
if (s.equals("android")) {
list.remove(s);
}
}
}
因为数组倒序遍历时即使发生元素删除也不影响后序元素的遍历。
remove2 方法运行会报 for-each 著名的并发修改异常 java.util.ConcurrentModificationException,因为迭代器内部会维护一些索引位置数据,要求在迭代过程中容器不能发生结构性变化(添加、插入、删除,修改数据不算),否则这些索引位置数据就失效了,修改的方式就是使用迭代器的 remove 方法替换 remove2 中的实现即可。
每期开班座位有限.0元试听抢座开始!
温馨提示 : 请保持手机畅通,咨询老师为您
提供专属一对一报名服务。