迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示。
一、概述
前面我们分析了大部分的Java集合框的集合类,比如:ArrayList、LinkedList、HashMap。他们各自的底层数据机构大都不同, ArrayList 是维护动态数组,LinkedList 是链表结构,HashMap 是依赖于哈希算法的数组和链表、红黑树。Java对这些常用的集合都实现了迭代器(Iterator),从而使我们能在不同的集合类使用同一的遍历方法。
把访问逻辑从不同类型的集合类中抽取出来,不用直接和集合进行打交道,而是控制 Iterator 向它发送向前向后的指令,就可以遍历集合,从而避免向外部暴露集合的内部结构。
二、Iterator
1. 遍历
迭代器是可以从前往后,或者从后往前遍历的。
为遍历不同聚集结构提供如:开始,下一个,是否有下一个,是否结束,当前哪一个等等的一个统一接口。
public interface Iterator<E> {
// 是否有下一个
boolean hasNext();
// 获取下一个
E next();
// 移除迭代器新返回的元素。
default void remove() {
throw new UnsupportedOperationException("remove");
}
// @since 1.8 传入一个方法: 可以对每个元素进行特定操作
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
JDK内置的集合类,大多都可以直接使用 Iterator 遍历:
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("list1");
arrayList.add("list2");
arrayList.add("list3");
printIterator(arrayList.iterator());
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("linked1");
linkedList.add("linked2");
linkedList.add("linked3");
printIterator(linkedList.iterator());
HashSet<String> set = new HashSet<>();
set.add("set1");
set.add("set2");
set.add("set3");
printIterator(set.iterator());
}
public static void printIterator(Iterator iterator){
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
2. fail-fast
使用 Iterator 遍历集合时,不能使用集合类的方法来添加或者删除来改变集合的大小。
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("list1");
arrayList.add("list2");
arrayList.add("list3");
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
// arrayList.add("cc");
arrayList.remove("list3");
}
}
运行时会抛 ConcurrentModificationException 异常。
唯一完全的的方法是使用 Iterator 的方法 remove
方法。
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("list1");
arrayList.add("list2");
arrayList.add("list3");
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()){
String item = iterator.next();
if (item.equals("list1")){
iterator.remove();
}
}
arrayList.forEach(System.out::println);
}
// 打印结果:
list2
list3
我们来看下 ArrayList 里面实现的 Iterator:
public Iterator<E> iterator() {
return new 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
// 获取当前 ArrayList的 版本号
int expectedModCount = modCount;
// 是否还有下一个元素
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
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];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
// 移除操作
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
// 重新复制一次 当前ArrayList的版本号
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();
}
// 检查 当前ArrayList的版本号 是否已发送变化
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
抛异常的核心方法是 checkForComodification()
,比较创建 Iterator 的时候的 ArrayList 的版本号和当前 ArrayList 的版本号是否一样,不一样则抛异常。ArrayList 的 add
和 remove
都会使 modCount
加1,所以在使用 Iterator 遍历的时候,调用集合本身的添加或删除就会改变modCount
从而造成前后不一致,就会抛异常。
当然,也不是绝对的,看源码我们可以发现,只有调用了checkForComodification
方法才会触发检查,而检查是否存在下一个hasNext
方法是不会触发检查的。所以我们可以在遍历到倒数第二个的时候调用集合的移除方法,从而在下一次执行 hasNext
的时候返回false。 然后退出循环,达到在 Iterator 里循环也能移除元素的目的:
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("list1");
arrayList.add("list2");
arrayList.add("list3");
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()){
String item = iterator.next();
if (item.equals("list2")){
arrayList.remove("list1");
}
}
arrayList.forEach(System.out::println);
}
打印结果为:
list2
list3
结果正常输出,不会报错,正常移除我们要移除的元素,当然也可以移除任意存在的元素(没屁用的知识又增加了)。 这种方法还是不提倡,推荐使用官方已经提供的 Iterator 的移除方法。
3. forEach
JDK 1.5的时候引入了增强for循环(for each)
。
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("list1");
arrayList.add("list2");
arrayList.add("list3");
for (String item : arrayList) {
System.out.println(item);
}
}
for each
只是语法糖,节省代码,节省操作,不用关心下标的起始值和终止值。本质还是 Iterator。
我们来看编译后的文件:
public static void main(String[] var0) {
ArrayList var1 = new ArrayList();
var1.add("list1");
var1.add("list2");
var1.add("list3");
// 本质还是使用 Iterator的 遍历
Iterator var2 = var1.iterator();
while(var2.hasNext()) {
String var3 = (String)var2.next();
System.out.println(var3);
}
}
可以看到,本质还是使用了 Iterator 的遍历,所以我们在使用 for each
的时候不要操作集合的删除或添加方法。会抛 ConcurrentModificationException 异常,原因刚刚分析了。
三、Iterable
Iterable 接口实现后的功能是‘返回’一个迭代器。是 Java 集合框架的顶级接口。
public interface Iterable<T> {
// 返回一个 迭代器
Iterator<T> iterator();
//@since 1.8 传入一个方法,对每个元素 进行特定操作
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
// @since 1.8 可分割迭代器: 并行处理
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
四、ListIterator
ListIterator 继承于 Iterator,增加了很多方法。
public interface ListIterator<E> extends Iterator<E> {
// 是否存在下一个
boolean hasNext();
// 获取下一个
E next();
// 游标前是否存在元素
boolean hasPrevious();
// 获取游标的前一个元素
E previous();
// 获取游标后面的 元素索引
int nextIndex();
// 获取游标前面的 元素索引
int previousIndex();
// 移除迭代器新返回的元素
void remove();
// 更新迭代器最后一次操作的元素为 E,也就是更新最后一次调用 next() 或者 previous() 返回的元素。
void set(E e);
// 插入一个元素
void add(E e);
}
相比于 Iterable 增加多个方法: 添加,修改,获取前一个元素,获取下标...
还是已 ArrayList 为例,看下它实现的 ListIterator :
// 返回一个 ListIterator对象
public ListIterator<E> listIterator() {
return new ListItr(0);
}
private class ListItr extends Itr implements ListIterator<E> {
// 游标位置从什么地方开始
ListItr(int index) {
super();
cursor = index;
}
public boolean hasPrevious() {
return cursor != 0;
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
// 获取游标的前一个元素
// 游标 和 迭代器的最新下标 都往前移动一格
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[lastRet = i];
}
// 修改方法
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
// 添加
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1; // 插入之后 游标 往后移动
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
可以很简单的知道原理,都是通过游标来操作对应数组的下标。
所以也能推导出为什么 ListIterator 不适用于 Set类型,而且 Set 类型也没有类型的增强的可添加的 Iterator 原因,因为本质还是操作数组下标,而 Set 的的插入是不能指定下标的。
ListIterator 和 Iterator 的区别
对比各自的源码API
我们可以发现:
- ListIterator 继承与 Iterator ,所以ListIterator 有 Iterator 的所有功能。
- ListIterator 还有往前获取元素的功能,可以双向遍历。
- ListIterator 可以获取当前迭代器的前后元素的下标。
- ListIterator 具备修改功能,可修改最新迭代的元素。
后记
Java集合可能暂告一段落。接下来可能分享java基础,JVM,多线程的理解。写文章不能是东抄抄西抄抄,还得有干货有自己的理解输出,所以会先自己理解源码然后参考权威书籍,尽最大努力输出干货,心得。
已经获得一个offer,总体来说比现在的好,虽然可能还不够好,不过我相信自己继续努力,终会更好。
量变引发质变,经常进步一点点,期待蜕变的自己。