一、迭代器
迭代:循环遍历的意思。遍历:挨个查看元素的行为。
迭代器:遍历集合元素的工具。
迭代器是为Collection系列的集合服务的。Map系列的集合通常要通过 keySet、values 、entrySet方法转换为Collection系列的集合再遍历。
1.1 Iterator接口
Collection系列的集合的底层实现各不相同,有数组、链表等,具体的遍历细节肯定不相同,但是Java为了统一标准,为所有Collection系列的集合设计了统一的迭代器的接口java.util.Iterator。
这个接口有2个抽象方法:
- boolean hasNext():判断是否还有元素可迭代
- E next():取出迭代器当前位置的元素,然后让迭代器走向下一个元素。
package com.mytest.iter;
import org.junit.Test;
import java.util.*;
public class TestIterator {
@Test
public void test1(){
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "hello","java","world");
//这里方法是Collection接口提供的
Iterator<String> iterator = list.iterator();//这句代码可以得到一个迭代器对象
while(iterator.hasNext()){
String s = iterator.next();
System.out.println(s);
}
}
@Test
public void test2(){
HashMap<Integer, String> map = new HashMap<>();
map.put(1,"hello");
map.put(2,"world");
map.put(3,"java");
/*
Map接口及其实现类,都没有实现Iterable接口,因此不能对Map及其实现类,直接使用foreach循环和Iterator迭代器进行遍历。
只能将Map先转为Collection系列的集合。通过(1)keySet(2)values(3)entrySet方法转换
Map与Collection类型无父子类关系,怎么能转换呢?
因为这里的转换,不是强制类型转换。而是将元素重新组装到新的集合类型中。
*/
Set<Map.Entry<Integer, String>> entries = map.entrySet();
Iterator<Map.Entry<Integer, String>> iterator = entries.iterator();
while(iterator.hasNext()){
//现在Set中元素的类型是 Map.Entry类型,它有2个属性,属性的类型分别是Integer和String
Map.Entry<Integer, String> s = iterator.next();
System.out.println(s);
}
}
}
1.2 foreach循环与Iterator有什么关系?
foreach循环是一种语法糖。
语法糖(Syntactic Sugar)是指在编程语言中添加的某种语法,这种语法本身不会增加语言的功能,但是可以使程序更加简洁易读,减少编写代码时的重复工作。语法糖通常是为了提高开发效率和代码可读性而设计的。
foreach在遍历数组时,其实本质上仍然普通for循环。
foreach在遍历集合时,其实本质上仍然迭代器。
1.3 Iterable接口
Comparable VS Comparator
Iterable VS Iterator
形容词 VS 名词
Comparable:是要比较大小的元素类自己实现的。 Comparator:是需要单独一个类(有名字,或匿名)来实现它。
Iterable:需要被遍历的集合自己实现的。这里就是所有Collection系列的集合实现。 Iterator:是需要单独一个类(有名字,或匿名)来实现它。
凡是实现Iterable接口的集合,都可以使用foreach循环进行遍历,本质上都是使用Iterator进行遍历。在这些集合内部都会有一个单独的类,来实现Iterator接口。
例如:ArrayList 实现了 Iterable接口,所以它支持foreach循环。 ArrayList的内部类有一个内部类 Itr ,它实现Iterator接口。
例如:HashSet 实现了 Iterable接口,所以它支持foreach循环。HashSet 的内部本质上是HashMap,HashMap类有一个内部类KeyIterator ,它实现Iterator接口。
思考:若自写的MyArrayList,和MyLinkedList 集合,它们是模仿ArrayList和LinkedList,能不能直接使用foreach循环遍历呢?
- 如果没有实现Iterable接口,就不支持。实现了Iterable接口,就支持。
- 实现Iterable接口,就要重写 Iterator iterator()方法,以便可以获取到Iterator的对象。
1.4 列表迭代器ListIterator
列表是指所有的List系列的集合,与Set、Map等无关。
ListIterator是Iterator的子接口。但是,它比Iterator功能更强大。它在遍历List集合的时候:
- 可以实现从头到尾遍历,也可以实现从尾到头遍历
- 在遍历时,可以获取元素的下标
- 在遍历时,可以实现增、删、改、查
package com.mytest.iter;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.ListIterator;
public class TestListIterator {
@Test
public void test1(){
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "hello","java","world");
//演示从头到尾遍历
ListIterator<String> listIterator = list.listIterator();
while(listIterator.hasNext()){
String s = listIterator.next();
System.out.println(s);
}
}
@Test
public void test2(){
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "hello","java","world");
//演示从尾到头遍历
ListIterator<String> listIterator = list.listIterator(list.size());
//迭代器一开始 [size]位置,第一次previous()取[size-1]位置的元素
while(listIterator.hasPrevious()){//判断前面还有没有元素可迭代器
String s = listIterator.previous();//previous取迭代器当前位置的前一个位置的元素
System.out.println(s);
}
}
@Test
public void test3(){
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "hello","java","world");
//在所有包含"a"字母的单词后面添加一个“test”
//演示从头到尾遍历
ListIterator<String> listIterator = list.listIterator();
while(listIterator.hasNext()){
String s = listIterator.next();
if(s.contains("a")){//这里contains是String类的方法
listIterator.add("test");//迭代器的add方法,不是集合的add方法
}
}
//再次查看结果
System.out.println(list);//[hello, java, test, world]
}
@Test
public void test4(){
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "hello","java","world");
//把所有包含"a"字母的单词改为大写
//演示从头到尾遍历
ListIterator<String> listIterator = list.listIterator();
while(listIterator.hasNext()){
String s = listIterator.next();
if(s.contains("a")){//这里contains是String类的方法
listIterator.set(s.toUpperCase());//迭代器的set方法,不是集合的set方法
}
}
//再次查看结果
System.out.println(list);//[hello, JAVA, world]
}
@Test
public void test5(){
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "hello","java","world");
//删除所有包含"a"字母的单词
//演示从头到尾遍历
ListIterator<String> listIterator = list.listIterator();
while(listIterator.hasNext()){
String s = listIterator.next();
if(s.contains("a")){//这里contains是String类的方法
listIterator.remove();//迭代器的remove方法,不是集合的remove方法
}
}
//再次查看结果
System.out.println(list);//[hello, world]
}
}
1.6 foreach或迭代器遍历过程中调用集合的add和remove问题
遍历Collection系列集合有更简便的foreach循环,为什么还要将Iterator?
- (1)foreach本质上还是Iterator
- (2)少量集合方法的源码中,使用的是Iterator
- (3)面试题中,出现了一种新的面试题,如本节标题
当我们在foreach或迭代器遍历集合的过程中,如果调用了集合的add、remove、sort等会影响集合元素个数,元素顺序的方法时,都会有风险,要么出现漏删,要么出现ConcurrentModificationException并发修改异常。Java中的集合设计这个“快速失败”机制就是为了预防不确定的问题。
设计思路是给集合增加了一个modCount的变量,它时刻记录集合元素个数,元素顺序发生的次数,每变一次,modCount就+1。迭代器遍历集合的过程中,会时刻检查modCount是否发生变化,如果是通过迭代器的add、remove等方法操作的集合,它会及时同步 modCount 和expectedModCount的值。如果通过集合的add、remove等方法,那么迭代器就无法实现同步,就会尽快抛出ConcurrentModificationException异常实现快速失败。
结论:
千万
不要在foreach或迭代器遍历过程中调用集合的add和remove等方法。建议:
如果你要根据条件删除,JDK8之后请用 removeIf方法。
如果你要在遍历过程中添加,当然这个仅限于列表集合,那么只能使用迭代器的add方法。
迭代器快速失败机制分析:
ArrayList集合的相关变量:
Object[] elementData :数组,用于存储元素
int size:用于记录元素个数
int modCount:主要用于记录元素个数变化次数,添加或删除元素等都会使得modCount值++
sort和replaceAll方法也会使得modCount++,因为这两个方法会使得元素顺序或元素值发生大变化,导致未遍历过的元素跑到迭代器已遍历过的位置上去,或已遍历过的元素值发生修改
ArrayList内部类Itr的相关变量:
int cursor:迭代器当前游标值,即迭代器当前指向elementData元素的下标值
int lastRet:迭代器刚刚访问过的元素下标值,如果迭代器还未访问过元素,或者刚刚访问过的元素已被删除,那么它的值为-1,表示该元素不存在了。
int exepectedModCount:迭代器“预计的”modCount值,它应该与ArrayList的modCount值相等,否则就说明集合在迭代器之外修改了集合,对集合做了添加或删除元素操作。
1.7 集合与数组的区别
| 数组 | 集合 | |
|---|---|---|
| 元素是否可以是基本数据类型 | 可以 | 不可以,集合只能装对象 |
| 是否支持自动扩容 | 不支持 | 支持 |
| 是否支持多种数据结构 | 否 | 很丰富 |
列表迭代器与普通迭代器有什么区别?
| 普通迭代器Iterator | 列表迭代器ListIterator | |
|---|---|---|
| 关系 | 父 | 子 |
| 遍历哪些集合 | 所有Collection系列 | 仅限于遍历List系列 |
| 支持从左往右/从前往后遍历 | 支持 | 支持 |
| 支持从右往左/从后往前遍历 | 不支持 | 支持 |
| 遍历过程中支持删除吗(迭代器的删除) | 支持 | 支持 |
| 遍历过程中支持添加和修改(迭代器的添加和修改) | 不支持 | 支持 |
| 遍历List的过程中是否支持获取下标 | 不支持 | 支持 |
问:Iterable 与 Iterator有什么关系 或 区别?
答:Iterable 依赖于 Iterator。Iterable接口有一个抽象方法: Iterator iterator();
Iterable 是集合类本身实现, Iterator是要单独的类,通常是集合中的内部类来实现。例如:ArrayList集合就实现了Iterable 接口,ArrayList的内部有一个内部类Itr实现类Iterator接口。
问:foreach循环与迭代器有什么关系?
答:foreach循环的底层就是使用迭代器来完成遍历集合的。凡是实现Iterable接口的集合,都可以使用foreach循环遍历。
问:在foreach或Iterator遍历集合的过程中,能不能调用集合的add,remove,sort等方法?
答:千万不能。如果调用了,可能结果不正常,或者发生ConcurrentModificationException异常。
1.8 其他
快捷键:
- 快速查看一个类的成员有哪些,打开这个类,按快捷键Ctrl + F2
- 查看接口的实现类重写接口方法的源码,按快捷键
Ctrl + Alt + B,然后选择接口的实现类