天天看點

設計模式-疊代器模式及應用

在程式設計中,要通路集合對象時。一般通過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重置,導緻删除異常。

✨✨ 歡迎🔔訂閱個人的微信公衆号 享及時博文更新

設計模式-疊代器模式及應用

繼續閱讀