所有的集合類(List、Set…)都實現自 Collection 接口,而 Collection 接口又繼承於 Iterable 接口,因此可以說所有的集合類(List、Set…)都實現了 Iterable 接口
當某個類實現 Iterable 接口時,我們就能稱這個類是一個 “可數” 的類,也就是可以使用 iterator()
獲取一個迭代器 Iterator,然後使用這個 Iterator 實例去遍歷這個類,因此所有的 Collection 類都能夠使用迭代器 Iterator 來遍歷
Iterable 接口
public interface Iterable<T> {
//當某個類實現Iterable接口的話,就能獲取到迭代器iterator,然後就能使用這個iterator去遍歷此類
Iterator<T> iterator();
}
Iterator 接口
如果某個類實現了 Iterable 接口,那麼他也需要創建一個內部類去實現一個 Iterator 類,讓調用 Iterable 接口中的 iterator() 時,能夠獲取到一個 iterator 實例
public interface Iterator<E> {
//是否有下一個元素
boolean hasNext();
//取得下一個元素
E next();
//刪除最後一個獲取的元素,因此調用remove()前一定得先調用一次next()
void remove();
}
至於此 Iterator 接口怎麼實現,就看各個集合實現類如何定義 “下一個元素”,像是 ArrayList 的下一個元素就是 element[index+1],而 HashMap 的下一個元素則是 hash table 數組中儲存的下一個 entry
另外可以想像 Iterator 像是一個游標一樣,一開始停在最前面,然後不停的往後走(只能向後移動),且此游標每次都是停在元素和元素的中間,當調用 next 時,迭代器就越過下一個元素,並返回剛剛越過的那個元素的引用
使用迭代器 Iterator 遍歷 ArrayList
public class Main {
public static void main(String[] args) {
//ArrayList實現了Collection接口,因此他也實現了Iterable接口,所以他可以使用iterator迭代器來遍歷
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
//調用Iterable接口中的iterator()取得此ArrayList的迭代器實例
Iterator<String> its = list.iterator();
//使用Iterator接口的hasNext()、next()來遍歷此ArrayList集合實現類
while (true) {
if (its.hasNext()) {
String s = its.next();
System.out.println(s);
} else {
break;
}
}
}
}
而再進一步說,當某個類能使用迭代器 Iterator 來遍歷時,就能使用 java 提供的 foreach 語法糖來遍歷此類(foreach語法糖其實就是簡化的 iterator()
)
foreach 實際上會被編譯器編譯成使用迭代器 iterator()
去遍歷集合,因此能使用 foreach 的,都是得實現 Iterable 接口的集合類 Collection 們,像是 List、Set
所以 Map 就沒有辦法直接使用 foreach(因為 Map 沒有實現 Iterable 接口),只有他的 map.entrySet()
、map.keySet()
、map.values()
這種返回一個集合類的方法,才能使用 foreach
public class Main {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
//原代碼,使用語法糖的foreach
for (String s : list) {
System.out.println(s);
}
//實際上會被編譯成使用iterator去遍歷
for (Iterator<String> its = list.iterator(); its.hasNext(); ) {
String s = its.next();
System.out.println(s);
}
}
}
為什麼 Iterator 要額外使用內部類去實現,而不是 ArrayList 直接實現此接口 ?
如果看過 Collection 類的源碼(以ArrayList為例),可以發現 ArrayList 類並不是由 ArrayList 去實現 Iterator 接口,而是 ArrayList 有一個內部類 Itr,專門去實現 Iterator 接口,而 ArrayList 的 iterator()
方法,只是去創建一個內部類 ArrayList.Itr 的實例而已
//ArrayList不實現Iterator接口,反而是由他的內部類進行實現
public class ArrayList<E> extends AbstractList<E> {
//調用list.iterator()可以取得此list的迭代器
public Iterator<E> iterator() {
return new Itr(); //實際上就是去創建一個內部類的實例
}
//ArrayList中的內部類Itr,專門實現Iterator接口
private class Itr implements Iterator<E> {
int cursor; //記錄當前迭代到哪裡
public boolean hasNext() { ... }
public E next() { ... }
public void remove() { ... }
}
}
要這樣設計是因為一個集合類可能同時有多個迭代器去遍歷他,而每個迭代器遍歷到集合的哪裡,是每個迭代器自己的事情,彼此不互相干涉,因此才需要額外使用一個內部類去實現迭代器的 Iterator 接口
list.iterator()
,就能取得一個全新的、不受別人影響的迭代器供自己使用,而迭代器彼此之間也不會互相干涉hasNext()
、next()
、remove()
方法,而使用內部類才能無條件的取用外部類的所有信息(包含 private 的變量和方法),因此才需要將 Iterator 提取成接口,讓每個集合自己使用內部類去實現 Iterator 接口為什麼 Iterator 接口,只有 hasNext()
、next()
、remove()
方法,而沒有 add(E)
方法 ?
iterator.add()
方法,理論上來說,應該是要在當前這個元素 E1 後面新增一個元素 E2,使得下次遍歷此集合時,E2 一定會出現在 E1 後面,也就是 [….E1, E2, ….]add()
方法是以這個語意為前提的話,那麼迭代器不提供此方法是很合理的,對於有序的集合(像是ArrayList)來說,在此元素後面新增一個元素是一個很簡單的事情,但是對於無序的集合(像是HashSet)來說,不能保證新插入的這個元素 E2 一定會在 E1 後面(因為還得計算 HashCode),如此就違反了 add()
的語意了,這也就是為什麼 Iterator 接口不提供 add()
方法add()
方法,因此 Iterator 接口沒必要提供這個方法