目录
1 增强for和集合中的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迭代陷阱(单线程和多线程都有问题)。
4 使用普通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());
}
参考博客
1 https://blog.csdn.net/qq_27127145/article/details/83932318
2 https://www.cnblogs.com/jinggod/archive/2018/02/07/8424868.html
3 https://www.cnblogs.com/kobelieve/p/10626473.html