java中Iterator的思考
前两天,一哥们拿着同一个问题连续找了我两次。一开始以为没什么说的东西,后来越研究越觉得有意思,今天闲来无事,写出来跟大家分享。
问题是这样的:“迭代集合时,Iterator it=c.iterator() 返回的到底接口Iterator的哪个实现类?”。刚开始我随口就是“查查API不就知道了么”,后来证明查API还真就“不知道”。API显示Iterator只有三个实现类(BeanContextSupport.BCSIterator, EventReaderDelegate, Scanner),但是哪一个都不像是跟迭代有关的。后来查源码,发现Iterator设计和实现的精妙之处。
jdk源码 Iterator接口定义如下:
1
2
3
4
5
|
public interface Iterator<E> { boolean hasNext(); //判断容器内是否还有可供访问的元素 E next(); //返回迭代器刚越过的元素的引用,返回值是Object,需要强制转换成自己需要的类型 void remove(); //删除迭代器刚越过的元素 } |
我们都知道,其实在编程过程中经常使用的,也只有hasNext()和next()。一般我们都是这么进行迭代:
1
2
3
4
5
|
Iterator it=c.iterator(); while (it.hasNext()){ Object o=it.next(); //do something } |
不难发现,无论迭代的是List集合还是Set集合,也无论集合底层是采用数组实现的ArrayList、Vector、HashSet,或者是采用链表实现的LinkedList、又或者是采用二叉树实现的TreeSet,统统都是通过统一的方法hasNext()、next()来判断、获取下一个元素,但是具体的内部操作肯定是不一样的,那Iterator是怎么做到的呢?其实,并不是Iterator怎么做到,而是每一个集合类自己分别来进行实现的。下面我以ArrayList为例,跟大家一起分析一下jdk的精妙实现:
众所周知,ArrayList的内部实现采用数组,所以我们只需要记录相应位置的索引就可以了,其方法的实现也是比较简单的。它通过定义内部类内部类,来实现Iterator接口来实现的,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
private class Itr implements Iterator<E> { int cursor = 0 ; //cursor从0开始,表示下一个元素的索引位置 int lastRet = - 1 ; //lastRet从-1开始,表示上一个元素的索引位置 int expectedModCount = modCount; //记录对集合修改的次数,主要是用于实现ArrayList集合的快速失败机制 /** *hasNext()只需判断当前位置是不是处在最后一个了即可. */ public boolean hasNext() { return cursor != size(); } /** * next()实现其实也是比较简单的,只要返回cursor索引位置处的元素即可,然后修改cursor、lastRet即可. */ public E next() { checkForComodification(); //主要用来判断集合的修改次数是否合法,即用来判断遍历过程中集合是否被修改过. try { E next = get(cursor); //从底层实现数组里取得当前元素 lastRet = cursor++; //lastRet+1 return next; //返回当前元素 } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } /** * 调用ArrayList本身的remove()方法删除lastRet位置元素,然后修改modCount即可. */ public void remove() { if (lastRet == - 1 ) throw new IllegalStateException(); checkForComodification(); try { AbstractList. this .remove(lastRet); //从底层实现数组里删除上一个元素 if (lastRet < cursor) cursor--; //由于当前元素要填补到上一个元素的位置去,所以当前元素下标-1 lastRet = - 1 ; expectedModCount = modCount; //把集合的修改次数赋值给expectedModCount } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } } |
这就是ArrayList的Iterator接口的实现。有人会问“那岂不是每一个集合类都要提供对Iterator的实现啊?”,对!Iterator只提供方法,你要想使用其进行迭代遍历,就必须提供对它的迭代实现,记得以前java培训的老师这么讲过。实际上,LinkedList、Vector、HashSet、TreeSet等集合的Iterator实现也都采用类似的设计思路。
通过以上探究,我们不难看出,Iterator给我们提供了一种通用的遍历各种集合的方法,它可以把访问逻辑从不同类型的集合类中抽象出来,做到了访问代码和集合本身的解耦,从而避免向客户端暴露集合的内部结构。从此,客户端可以不直接和集合类打交道,它只需要控制Iterator,向它发送“向前”、“向后”、“取当前元素”、“删元素”的命令,就可以间接遍历和操作整个集合,并且这些客户端代码还是可以复用的。其实,这就是java中非常著名的Iterator设计模式。
书到用时方恨少,事非经过不知难。点滴积累,你就会成为技术上的巨人!