关于for丶foreach丶iterator 迭代器
1丶前言:我们在网上或者在讨论的时候往往会有这么一种说法:foreach 也是迭代器的一类,底层实现的是迭代器。但是这种说法并不严谨,也可以说这种说法对于我们正常的理解来说并不是能够完全理解。
2丶首先我来说出结论:for 丶 foreach 丶迭代器 这三个遍历方法中 不应该将他们三种遍历方法归在一起 而是将其分成三类)——不要将foreach 归为迭代器的一种遍历
(我们一步一步来,首先来简单介绍一下这是那种方法的区别)
3丶区别:
1)形式区别:
对于for循环,我们采用:
for(int i=0;i<arr.size();i++){…}
对于foreach:
for(int i:arr){…}
(在这里用int 类型举个例子)
对于迭代器的形式:
Iterator it = arr.iterator();
while(it.hasNext()){ object o =it.next(); …}
;
2)条件差别
for需要知道数组或者集合的大小,而且需要时有序的,不然无法遍历;
foreach和iterator不需要知道数组或者集合的大小,他们都是得到集合内的每一个元素然后进行处理;
3)多态差别
for和foreach都需要知道自己的集合类型,甚至要知道自己集合内的元素类型,不能实现多态。(
public static void main(String[] args) {
int[] ints = new int[]{1, 2, 3, 4};
int[] var2 = ints;
int var3 = ints.length;
for(int var4 = 0; var4 < var3; ++var4) {
int i = var2[var4];
System.out.println(i);
}
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(789);
coll.add(new Person(1001, “Tom”));
Iterator var7 = coll.iterator();
while(var7.hasNext()) {
Object object = var7.next();
System.out.println(object);
}
}
这差不多是增强for的底层源码 我们可以清楚的看到 当我们遍历数组的时候他会用for循环俩遍历,放我们遍历结合的时候他会用迭代器来使用(你可能感到这与上面我所得出的结论相违背,下面的讲解我会说明)
)
Iterator是一个接口类型,它不关心集合的累心和集合内的元素类型,因为它是通过hasnext和next来进行下一个元素的判断和获取,这一切都是在集合类型定义的时候就完成的事情。迭代器统一了对容器的访问模式,这也是对接口解耦的最好表现。(lIterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。)
4)用法差别
for一般可以用于简单的顺序集合,并且可以预测集合的大小(一定要有序,因为i这个索引,所以遍历是一定要有序);
foreach可以遍历任何集合或者数组,但是使用者需要知道遍历元素的类型
iterator是最强大的,它可以随之修改元素内部的元素。可以在遍历的时刻用remove()
而且iterator不需要知道元素类型和元素大小,通过hasnext()判断是否遍历完所有元素。
而且在对范型的集合进行遍历的时候,iterator是唯一的选择,就是因为不需要知道元素类型便可以遍历。
4丶代码分析:
我们来看一下代码:
import java.util.*;
public class TreeSetTest {
public static void main(String[] args) {
List list=new ArrayList();
list.add("1");
list.add("2");
Iterator<String> list1= list.iterator();
while (list1.hasNext()){
String next = list1.next();
if ("2".equals(next)){
list1.remove();
}
}
list1=list.iterator();
while (list1.hasNext()){
System.out.println(list1.next());
}
}
这样可以将“2”删除
import java.util.*;
public class TreeSetTest {
public static void main(String[] args) {
List list=new ArrayList();
list.add("1");
list.add("2");
for(String temp : list){
if("2".equals(temp))
list.remove(temp);
}
for(String temp : list){
System.out.println(temp);
}
}
我们运行这写代码会抛出异常
Exception in thread “main” java.util.ConcurrentModificationException
at java.base/java.util.ArrayList I t r . c h e c k F o r C o m o d i f i c a t i o n ( A r r a y L i s t . j a v a : 1009 ) a t j a v a . b a s e / j a v a . u t i l . A r r a y L i s t Itr.checkForComodification(ArrayList.java:1009) at java.base/java.util.ArrayList Itr.checkForComodification(ArrayList.java:1009)atjava.base/java.util.ArrayListItr.next(ArrayList.java:963)
at 集合复习.TreeSetTest.main(TreeSetTest.java:13)
ConcurrentModificationException的源码:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
这里设计一个java关于多线程的知识,因为iterator是禁止多线程操作的。java用了一个机制,就是modcount。记录对集合进行改变的数量,创建一个迭代器与当前集合是要紧耦合的。我不能再我遍历的时候同时去改变这个集合元素,不然会造成混乱。
(接口解藕:不管对象实现的过程,只关心他的结果。
比如我每天要去上课,每天都要定闹铃。但是我可以用闹铃实现,如果闹铃坏了,我可以用手机去继承闹铃的功能,用手机来实现闹铃的功能。
再比如,女人爱上了男人,男人也爱上女人,但我不管这个男人或者女人是谁只要是个男或者女人就行。
耦合理解相对比较容易,就是两个或两个以上的体系或两种运动形式间通过相互作用而彼此影响以至联合起来的现象。)
在这里我们已经知道是modcount不等于expectedmodecount造成的错误
我们可一看到报错的来源 at java.base/java.util.ArrayList$Itr.next(ArrayList.java:963)
用idea打开之后查看源码是Itr(Iterator接口的实现类)这个私有类里面的next是foreach擅自为我们进行构造的迭代器,而我们没有使用这个迭代器里面的remove()
但是我们在这时看自己的代码 是没有创建list的迭代器的,也就是说我们使用的是Arraylist本身自己的remove()方法。
我们再看一下各自的remove()的源码:
1丶Arraylist.remove():
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++;
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
}
Arraylist自身方法的remove是不会去调整exceptedmodcount的值,但是却增加了modcount,所以在foreach语句中使用next的checkmodcount的时候发生了错误。
2丶Itr私有迭代器的remove():
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount; ——>让 modCount 赋值给 expectedModCount 使其不报错
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
我们可以看到迭代器中的remove有意识的改变expectedmodcount来使其不报错。而foreach使用的Arraylist.remove,改变了modcount,所以才会出现报错现象。
可是你在这时会想到为什么删除“1”的时候他不报错?
那是是因为删除过后foreach会调用迭代器中的hasnext()进行判断,
代码如下:
public boolean hasNext() {
return cursor != size;
}
这个时候没有下一个元素,所以不会Arraylist.remove()后调用Itr.next()方法,所以不会进行判断。
Itr.next()的代码:
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
可以看到,在Itr.next()的一开始便检查modcount于expectedmodcount是否相同。
通过上面的了解,我们使用iterator为什么能够在遍历的时候进行删除操作也可以理解了:
在Itr.remove()方法内有意的将expectedmodcount赋值为modcount,所以不会抛出异常。
最后还是强调一句话:不要将foreach 归为迭代器的一种遍历。两者之间是有一定的区别的。
(本人第一次写博客,完全在自己的能力范围之内,如有不好的地方,还请多多包涵)。