在程式設計中,要通路集合對象時。一般通過for循環、foreach、疊代器等方式。Java中的集合提供都有疊代器,例如 Collection、List、Set、Map 都提供了疊代器,我們隻需要擷取相關疊代器,就可以對元素進行周遊。
本文講解的就是疊代器模式,JDK雖然已經提供了疊代器,自己如何實作疊代器模式,來周遊集合或者聚合的對象。
疊代器模式定義與結構
疊代器(Iterator)模式的定義:疊代器提供一個順序方法指定類型聚合對象的操作,而不暴露聚合對象的内部細節。
1.疊代器模式的結構:
疊代器模式是通過将聚合對象的周遊行為分離出來,抽象成疊代器類來實作的,其目的是在不暴露聚合對象的内部結構的情況下,讓外部代碼透明地通路聚合的内部資料。
疊代器模式主要包含以下角色。
- 抽象聚合(Aggregate)角色:定義存儲、添加、删除聚合對象以及建立疊代器對象的接口。
- 具體聚合(ConcreteAggregate)角色:實作抽象聚合類,傳回一個具體疊代器的執行個體。
- 抽象疊代器(Iterator)角色:定義通路和周遊聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
- 具體疊代器(Concretelterator)角色:實作抽象疊代器接口中所定義的方法,完成對聚合對象的周遊,記錄周遊的目前位置。
結構圖:
代碼案例:
- 抽象聚合
public interface Aggregate {
//增加元素
public void add(Object obj);
//删除元素
public void remove(Object obj);
//擷取疊代器
public Iterator getIterator();
}
- 具體聚合 1
public class LinkedListAggregate implements Aggregate {
private List<Object> list = new LinkedList<>();
@Override
public void add(Object obj) {
list.add(obj);
}
@Override
public void remove(Object obj) {
list.remove(obj);
}
@Override
public Iterator getIterator() {
return (new Iterator(list));
}
}
- 具體聚合 2
public class ListAggregate implements Aggregate{
private List<Object> list = new ArrayList<Object>();
@Override
public void add(Object obj) {
list.add(obj);
}
@Override
public void remove(Object obj) {
list.remove(obj);
}
@Override
public Iterator getIterator() {
return (new Iterator(list));
}
}
- 抽象疊代器
public abstract class AbstractIterator {
//第一個元素
protected abstract Object first();
//下一個元素
protected abstract Object next();
//是否存在下一個元素
protected abstract Boolean hashNext();
}
- 具體疊代器
public class Iterator extends AbstractIterator {
//疊代對象
private List<Object> list = null;
//下标
private int index = -1;
//疊代器構造器
public Iterator(List<Object> list) {
this.list = list;
}
protected Object first() {
index = 0;
return list.get(index);
}
protected Object next() {
Object object = null;
if (hashNext()) {
object = list.get(++index);
}
return object;
}
protected Boolean hashNext() {
if (index < list.size() -1 ) {
return true;
}else {
return false;
}
}
}
- 使用方
public class Client {
public static void main(String[] args) {
// list 疊代
ListAggregate listAggregate = new ListAggregate();
listAggregate.add("a");
listAggregate.add("b");
listAggregate.add("c");
Iterator iterator = listAggregate.getIterator();
while (iterator.hashNext()) {
Object next = iterator.next();
System.out.println(next);
}
System.out.println("===================");
// LinkedList 疊代
LinkedListAggregate LinkedAggregate = new LinkedListAggregate();
LinkedAggregate.add("1");
LinkedAggregate.add("2");
LinkedAggregate.add("3");
Iterator iterator1 = LinkedAggregate.getIterator();
while (iterator1.hashNext()) {
Object next = iterator1.next();
System.out.println(next);
}
}
}
執行結果:
a
b
c
===================
1
2
3
案例代碼結構圖:
通過執行結果驗證,自定義的疊代器實作了和JDK提供的疊代器類似的功能,可以對集合對象進行疊代。那這裡雖然我們清楚ArrayList底層用的是數組,而LinkedList底層資料結構使用了連結清單的方式,對于我們的疊代器來說,周遊的關鍵是通過數組或者連結清單的下标對集合進行周遊。
疊代器模式的優缺點
疊代器模式的優點有很多,主要有點如下;
優點:
- 通路集合對象時,隐藏了對象内部的表示,使用者隻需擷取疊代器完成集合對象的疊代;
- 周遊任務交由疊代器完成,無需關注疊代實作細節;
- 支援拓展,可以自定義疊代方式;
- 封裝良好,為不同的集合提供統一的疊代器接口。
缺點:
- 新的資料結構,需要實作不同的疊代器具體實作。增加類的數量
疊代器模式在源碼中的使用
1.JDK源碼中的使用
因為疊代器模式比較簡單,且一般的集合都提供相應的疊代器。這裡就看一下JDK是如何實作疊代器的,首先看下疊代器接口 Iterator
public interface Iterator<E> {
boolean hasNext(); // 是否存在下一個對象元素
E next(); //元素
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
除了提供兩個基本的方法外,還提供兩個預設的方法,jdk1.8後的寫作方式。然後舉例兩個常見的集合,看下是如何實作疊代器的方法。
接以
ArrayList
為例。
ArrayList
實作了
List
接口,
List
接口繼承了
Collection
接口,
Collection
接口繼承了
Iterable
接口,
Iterable
接口中包含了一個
iterator( )
方法,是以
ArrayList
就需要實作該
iterator( )
方法。該方法的實作很簡單,就是傳回一個實作了
Iterator
接口的疊代器執行個體。
ArrayList 類中的疊代器是以内部類的形式實作的,由于内部類可以直接通路外部類的成員變量,是以該疊代器内部類可以很友善地實作 Iterator 接口中的 hasNext( ) 和 next( ) 方法。ArrayList 中的疊代器内部類名字是 Itr,
源碼如下:
/**
* 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;
Itr() {}
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;
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();
}
}
Itr
是
ArrayList
中實作了
Iterator
接口的内部類,且在實作
hasNext()
和
next()
方法時,可以很友善地通路
ArrayList
的成員變量
size
和
elementData
數組。
ListItr 是對 Itr的拓展 内部類增加了 hasPrevious() 方法,主要用于判斷是否還有上一個元素。
源碼:
/**
* An optimized version of AbstractList.ListItr
*/
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();
}
}
}
拓展
1.疊代時改變集合中的元素,為什麼會導緻異常?
先看下一段代碼:
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Iterator<String> iterator = list.iterator();
iterator.next();
list.remove("a");
iterator.next();
Arraylist集合在增删元素後會報錯,
怎麼确定在周遊時候,集合有沒有增删元素呢?在ArrayList中定義一個成員變量
modCount
,記錄集合
被修改的次數,集合每調用一次增加或删除元素的函數,就會給
modCount
加1。當通過調用集合上的
iterator()
函數來建立疊代器的時候,我們把modCount值傳遞給疊代器的
expectedModCount
成員變量,之
後每次調用疊代器上的
hasNext()
、
next()
、
currentItem()
函數,我們都會檢查集合上的modCount是否等于
expectedModCount
,也就是看,在建立完疊代器之後,
modCount
是否改變過。
如果兩個值不相同,那就說明集合存儲的元素已經改變了,要麼增加了元素,要麼删除了元素,之前建立的疊代器已經不能正确運作了,再繼續使用就會産生不可預期的結果,是以我們選擇fail-fast解決方式,抛出運作時異常,結束掉程式。
上面列舉的案例,當周遊下一個元素後,删除集合中a的元素,再去周遊時,
modCount
已經更改,兩個值不相等,直接抛出異常。
2.在使用ArrayList疊代器周遊的同時,能否周遊删除其中的元素呢?
看如下代碼:
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Iterator<String> iterator = list.iterator();
iterator.next();
iterator.remove();
iterator.remove();
這裡會報
java.lang.IllegalStateException
異常,
Java疊代器中提供的remove()方法還是比較雞肋的,作用有限。它隻能删除遊标指向的前一個元素,而且一個next()函數之後,隻能跟着最多一個remove()操作,多次調用remove()操作會報錯。
當然去看源碼的話,最為直接清楚。
直接隻截取一段,疊代器全部的源碼上面已經有貼過:
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;
// next()函數将cursor遊标後移一位,lastRet 置為目前元素下标
return (E) elementData[lastRet = i];
}
// 删除
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
//删除後 lastRet置為-1
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
疊代器類新增了一個lastRet成員變量,用來記錄遊标指向的前一個元素。通過疊代器去删除這個元素的時候,可以更新疊代器中的遊标和lastRet值,來保證不會因為删除元素而導緻某個元素周遊不到。如果連續調用疊代器remove,會因為删除後沒有調用next(),上個删除時lastRet重置,導緻删除異常。
✨✨ 歡迎🔔訂閱個人的微信公衆号 享及時博文更新