增强for循环不能进行remove或add操作引发的对增强for循环和迭代器的整理

目录

1 增强for和集合中的forEach方法的底层是通过迭代器实现的

2 使用场景

3 迭代器Iterator

4  使用普通for循环与使用迭代器iterator的对比

        1)forEach循环能否完全替代普通for循环

        2)效率上对比

5 map中能不能使用forEach?

参考博客


 

1 增强for和集合中的forEach方法的底层是通过迭代器实现的

         1)增强for循环也称之为foreach循环

         2)如果是实现了Iterable接口的类或者是数组对象都可以使用foreach循环(增强for循环)或者集合的forEach方法(map没有实现Iterable接口,因此map类不能直接使用foreach循环)。通过反编译可以发现,增强for循环就是通过迭代器来实现的,是简化后的普通for循环,

示例代码:

将代码反编译: 看看编译器是如何处理 集合中的for-Each循环的?

public static void main(String args[])

{

List list = new LinkedList();

list.add("aa");

list.add("bb");

for(String item:list)

{

if("bb".equals(item))

list.add("cc");

}
}

我们看一下上面例子的 反编译代码:

public static void main(String args[])

{

List list = new LinkedList();

list.add("aa");

list.add("bb");

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

{

String item = (String)iterator.next();

if("bb".equals(item))

list.add("cc");

}

}

  与数组类似,编译器最终也就是将集合中的for-Each循环处理成集合的普通for循环。 而集合的Collection接口通过扩展Iterable接口来提供iterator()方法。那么我们换一个角度,是不是只要实现 Iterable接口,提供iterator()方法,也可以使用 for-Each循环呢?来看个例子:

class MyList<T> implements Iterable<T>{

private ArrayList<T> list = new ArrayList<>();

public void addId(T id){

list.add(id);

}

public boolean removeId(T id){

return list.remove(id);

}

@Override

public Iterator<T> iterator() {//扩展自Iterable接口

//为了简单起见,就直接使用已有的迭代器

return list.iterator();

}

public static void main(String[] args) {

MyList<String> myList = new MyList<>();

myList.addId("666999");

myList.addId("973219");

//for-Each

for(String item:myList){

System.out.println(item);

}

}

}

  上面的例子编译通过,并且运行无误。所以,只要实现了Iterable接口的类,都可以使用for-Each循环来遍历。

2 使用场景

增强for一般只用来遍历输出内容,不对遍历的集合内容进行修改

        1)示例:

        2)代码验证

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

public class TestForList {

public static void main(String[] args) {

List<String> list = new ArrayList<>();

list.add("111");

list.add("222");

for (String item : list) {

if ("222".equals(item)) {

list.remove(item);

}

}

}
}

//输出:Exception in thread "main" java.util.ConcurrentModificationException

//at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)

//at java.util.ArrayList$Itr.next(ArrayList.java:859)

  3)反编译分析
   因为foreach循环是Java提供的一种语法糖,所以我们用反编译工具将以上代码编译后看看:

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

public class TestForList {

public TestForList() {

}

public static void main(String[] args) {

List<String> list = new ArrayList();

list.add("111");

list.add("222");

Iterator var2 = list.iterator();

while(var2.hasNext()) {

String item = (String)var2.next();

if ("222".equals(item)) {

list.remove(item);

}

}

}

}

       显然,foreach循环实际上还是用Iterator迭代器while循环。根据堆栈信息,查看源码,可以看到是当调用ArrayList里的内部类Itr的checkForComodification()方法报错:

   那我们看看modCount和expectModCount是什么?

  - modCount是ArrayList中的一个成员变量。它表示该集合实际被修改的次数,通过查询ArrayList中的add方法和remove方法,会发现没操作一次add或remove,modCount都会改变。

  - expectedModCount 是 ArrayList中的一个内部类Itr中的成员变量。expectedModCount表示这个迭代器期望该集合被修改的次数。其值是在ArrayList.iterator方法被调用的时候初始化的。只有通过迭代器对集合进行操作,该值才会改变。

  - Itr是一个Iterator的实现,使用ArrayList.iterator方法可以获取到的迭代器就是Itr类的实例。

   再看到remove方法的核心操作:

        add方法也会改变modCount的参数。可以看到,它只修改了modCount,并没有对expectedModCount做任何操作,此时会报ConcurrentModeificationException()异常。

        注:如果真的想要在for-each中执行删除操作,也可以采用迭代器的remove操作,他会同时改掉expectedModCount数值。

3 迭代器Iterator

源码分析:

  我们来分析一下Iterator源码,主要看看为什么在集合迭代时,修改集合可能会抛出ConcurrentModificationException异常。以ArrayList中实现的Iterator为例。

       先来看一下ArrayList.iterator()方法,如下:

public Iterator<E> iterator() {

return new Itr();

}

      iterator()方法直接创建了一个类Itr的对象。那就接着看 Itr类的定义吧!发现Itr其实是ArrayList的内部类,实现了 Iterator 接口。

/**

* An optimized version of AbstractList.Itr

*/

private class Itr implements Iterator<E> {

int cursor; // 当前的索引值,index of next element to return

int lastRet = -1; // index of last element returned; -1 if no such

int expectedModCount = modCount;



public boolean hasNext() {

return cursor != size;

}



@SuppressWarnings("unchecked")

public E next() {

checkForComodification();

int i = cursor;

if (i >= size)

throw new NoSuchElementException();

//ArrayList的底层数组

Object[] elementData = ArrayList.this.elementData;

if (i >= elementData.length)

throw new ConcurrentModificationException();

cursor = i + 1;

return (E) elementData[lastRet = i];

}



public void remove() {

if (lastRet < 0)

throw new IllegalStateException();

checkForComodification();



try {

ArrayList.this.remove(lastRet);

cursor = lastRet;

lastRet = -1;

//再次更新 expectedModCount

expectedModCount = modCount;

} catch (IndexOutOfBoundsException ex) {

throw new ConcurrentModificationException();

}

}



@Override

@SuppressWarnings("unchecked")

public void forEachRemaining(Consumer<? super E> consumer) {

Objects.requireNonNull(consumer);

final int size = ArrayList.this.size;

int i = cursor;

if (i >= size) {

return;

}

final Object[] elementData = ArrayList.this.elementData;

if (i >= elementData.length) {

throw new ConcurrentModificationException();

}

while (i != size && modCount == expectedModCount) {

consumer.accept((E) elementData[i++]);

}

// update once at end of iteration to reduce heap write traffic

cursor = i;

lastRet = i - 1;

checkForComodification();

}



final void checkForComodification() {

if (modCount != expectedModCount)

throw new ConcurrentModificationException();

}

}

    ArrayList.this.elementData是ArrayList的底层数组,上面这些方法都很简单,都是对ArrayList.this.elementData这个底层数组进行操作。

  重点看一下checkForComodification()方法,这个方法就是用来抛出 ConcurrentModificationException异常,这个方法也很简单,就是判断modCount与expectedModCount是否相等。modCount存储的AarryList中的元素个数。而expectedModCount则是对象创建时将modCount的值赋给它,也就是说expectedModCount存储的是迭代器创建时元素的个数。那么checkForComodification()方法其实在比较迭代期间,ArrayList元素的个数 是否发生了改变,如果改变了,就抛出异常。注意一下,expectedModCount除了在声明时赋值外,也在remove()方法中更新了一次。

小结:

  • 无论是在数组中还是在集合中,for-Each加强型for循环都是它们各自的普通for循环的一种“简写方式”,即两者意思上是等价的,但前者方便简单,建议多使用。

  • for-Each循环不能完全代替普通for循环,因为for-Each有一定的局限性。

  • for-Each循环只能用于 数组、Iterable类型(包括集合)。

  • 集合中的for-Each循环本质上使用了Ierator迭代器,所以要注意Itrator迭代陷阱(单线程和多线程都有问题)。

使用普通for循环与使用迭代器iterator的对比

        1)forEach循环能否完全替代普通for循环

                答案是不可以。foreach的局限性:

                (1)对元素只能是顺序访问

                (2)只能访问集合或数组中的所有元素

                (3)for each循环没有当前元素的索引,无法对指定的元素操作。 

        2)效率上对比

        效率上主要还是看是链表还是线性表,线性表:普通for更好,链表:迭代器。

        采用ArrayList对随机访问比较快,而for循环中的get()方法,采用的即是随机访问的方法,因此在ArrayList里,for循环较快;

        采用LinkedList则是顺序访问比较快,iterator中的next()方法,采用的即是顺序访问的方法,因此在LinkedList里,使用iterator较快;

        从数据结构角度分析,for循环适合访问顺序结构,可以根据下标快速获取指定元素.而Iterator 适合访问链式结构,因为迭代器是通过next()和Pre()来定位的.可以访问没有顺序的集合. 而使用 Iterator 的好处在于可以使用相同方式去遍历集合中元素,而不用考虑集合类的内部实现(只要它实现了 java.lang.Iterable 接口),如果使用 Iterator 来遍历集合中元素,一旦不再使用 List 转而使用 Set 来组织数据,那遍历元素的代码不用做任何修改,如果使用 for 来遍历,那所有遍历此集合的算法都得做相应调整,因为List有序,Set无序,结构不同,他们的访问算法也不一样.

5 map中能不能使用forEach?

       map本身没有实现迭代器接口,所以不能使用增强for循环或迭代器中的forEach方法(注:map中有自己的forEach方法),但是还想使用迭代器,需要通过map的entrySet()方法,返回集合set,再利用set的foreach循环。

        通过查看API文档可得知,Map集合没有实现Iterable接口,所以Map集合不能直接使用foreach循环

  

        但是它有一个entrySet方法(Map的迭代方法),它的返回类型是Set<Map.Entry<K,V>>

  

  我们知道Set接口下是实现了Iterable接口的:

  

        所以我们可以这样:

 HashMap<String, String> map = new HashMap<String, String>();

 map.put("001","张三");

 map.put("002","李四");

 map.put("003","王五");

 map.put("004","赵六");

 Set<Map.Entry<String, String>> entrys = map.entrySet();

 for(Map.Entry<String, String> entry : entrys){

         System.out.println("键:"+ entry.getKey()+" 值:"+ entry.getValue());

 }

参考博客

https://blog.csdn.net/qq_27127145/article/details/83932318

https://www.cnblogs.com/jinggod/archive/2018/02/07/8424868.html

https://www.cnblogs.com/kobelieve/p/10626473.html

https://blog.csdn.net/qj20134/article/details/80232497

https://www.cnblogs.com/shadowdoor/p/6852656.html

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值