很久之前便想动手写,无奈太懒。现在才开始动手,希望能尽快完成。
说起迭代器模式,不得不先说一下“镜像模式”(名字独创,不该称为模式)
所谓镜像模式,就是当函数返回引用对象时,理应返回该对象的拷贝,而不是直接返回该对象。
例如:
1 public class IDCard{ 2 string name; 3 bool sex; 4 //others... 5 } 6 7 public class IDCardList { 8 private List<IDCard> list; 9 public IDCardList() { 10 list = new ArrayList<IDCard>(); 11 //get list from files. 12 } 13 14 public List<IDCard> GetList(){ 15 return list; 16 } 17 }
当IDCardList::GetList() 直接将 list 返回自身让外部函数调用时,外部函数可能会直接对list进行修改(add 或者 remove),程序可能会产生未知错误。
所以,一般需写成:
public List<IDCard> GetList(){ return new ArrayList<IDCard>(list); }
这样会占用更多内存,但是这是没办法中的办法,除非可以保证调用者不会对原生list进行修改(程序由内部人员调用,且不供给二次开发者等等)。
如果使用c++的话,这样就好办多了,加上 const 就行。
时过境迁,我现在多使用C++ 和C#了。正因如此,才有了关于本文的思考。
有次,一同事问我关于C#链表的用法(时代久远),由于我使用List比较多(C#的List 相当于Java的ArrayList),没使用过链表(基础差),随机上网搜索了相关资料(还TM现学现卖),便解决了同事的问题(还好没出丑)。
在过程中发现了C#的LinkedList如此奇葩!!->
1.不实现IList的接口 (当时认为相当于Java中List接口)
2.外漏 LinkedListNode,人家Java都使用内部类Entry封装好了
与上面两点看来,可见当时的无知。完全以Java的角度先入为主了。//(其实当时我使用Java的链表也不多,到现在写起文章才记起是使用void add(int index, T o)这样的接口)
首先看看IList的接口代码,如下:
public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable { int IndexOf(T item); void Insert(int index, T item); void RemoveAt(int index); T this[int index] { get; set; } //索引 }
由于第一点,所以不能对链表使用索引。思考几许,方如梦初醒!Java中可以使用索引!所以迫不及待查看了Java中LinkedList的实现代码,更期待的是希望会有奇技淫巧之优化代码。
http://developer.classpath.org/doc/java/util/LinkedList-source.html 代码如下:
public T get(int index) { checkBoundsExclusive(index); return getEntry(index).data; } Entry<T> getEntry(int n) { Entry<T> e; if (n < size / 2){ e = first; while (n-- > 0) e = e.next; } else{ e = last; while (++n < size) e = e.previous; } return e; }
代码的确对索引有所优化,但是遍历一次链表下来,所需要的时间复杂度是O((n/2)! * 2) 啊!!!(这里可能有误,时间复杂度概念不好,望指教)
LinkedList::add(int index, T o) 和LinkedList::remove(int index, T o) 的都是浮云啊!!
所以,不难看出微软在设计.Net类库时,为何要将LinkedListNode独立提出来了吧?(效率问题,但真的是这原因吗?@老赵)
public LinkedListNode<T> AddAfter(LinkedListNode<T> node, T value) { this.VerifyReferencedNode(node); //判断node是否属于该链表 LinkedListNode<T> node2 = new LinkedListNode<T>((LinkedList<T>) this, value, node, node.forward); this.count += 1; this.version += 1; return node2; }
对比Java和C#的设计,谁好谁坏,不是我评论得了。
当然,Java并不就因此没办法了,他还有迭代器(Iterator)!并且在JDK 1.5 版本中,新增了foreach。配合使用真的是简单方便。
for(Iterator<IDCard> : idCardList) //这里假设class IDCardList 已经实现了 iterator 并将GetList()接口改成iterator() { //... }
在这里再说个题外话,以前读书时,听某个老师说写Java程序时最好不要使用Java Tiger和更新的版本,因为大量企业目前都使用Java 1.4,并且新版本还有很多未知的Bug,这意味着不能使用泛型和不能使用foreach。现在回想起真想干他一千次,Java出新版本本意就在更方便地编码,而他却叫人不要使用。可惜那时懵懂,对此还TM深信不疑。
在很长的一段时间,对迭代器都没太深入的认识(就是说你现在精通了?!?),直到见识了C# 的迭代器。
public interface IEnumerable<T> { IEnumerator<T> GetEnumerator(); } public interface IEnumerator<T> { bool MoveNext(); T Current { get; } void Reset(); }
相信很多人都对IEnumerable 和 IEnumerator 这两个类有疑惑,傻傻分不清楚。
IEnumerable 只返回一个IEnumerator,这样的设计是不是很多余?(2012.12.27:翻查Java的API才知道有Iterable。Iterator是1.2版本新增的,而Iterable是1.5版本才有的。[下面相关的文章删了 = =])
写到这里让我回想起一个在CSDN论坛中关于设计IEnumerable的讨论,非常有营养的,可惜找不到地址,哪位仁兄可以找下?^_^
大概就是:.Net的集合类库中,为何有GetEnumerator()的实现,而不能让用户外部注入迭代器,例如增加接口:void SetEnumerator(IEnumerator e);
还有为何在ArrayList中的成员变量_items,设为private而不是protected?(对哦为何呢?保护原基础类不受外界干扰吗?@老赵, 如果这样下面一句继承原类还是无法方便实现)
在原帖中还提到了为何需要迭代器模式,例如目前遍历ArrayList的顺序是从0~Count-1,使用自己的迭代器可以设为倒序迭代(Count-1~0),此时,我对迭代器有了更进一步的认识,但是依然模糊。
在后来的思考中,明白了IEnumerable 和 IEnumerator 的区别。
其中 IEnumerator 就相当于Java 中的Iterator
而接口IEnumerable 意为可迭代的,其实可以看成是创建迭代器的工具,但为何要有IEnumerable呢?请看:(C#)
void PrintDifferentStyle(IEnumerable<IDCard> enum) { foreach(var id in enum) { Console.WriteLine("姓名:"+id.Name+" 性别:"+id.Sex); } foreach(var id in enum) { Console.WriteLine("性别:"+id.Sex + " 姓名:"+id.Name); } //当然还可以输出HTML表格诸如此类的 }
想象下,如果没有IEnumerable而只有IEnumerator,一个迭代器能重复迭代吗?当然IEnumerator 还是有提供Reset功能的,但是Java API却没有。
void PrintDifferentStyle(IEnumerator<IDCard> enum) { while(enum.MoveNext()) { var id = enum.Current; Console.WriteLine("姓名:"+id.Name+" 性别:"+id.Sex); } enum.Reset(); while(enum.MoveNext()) { var id = enum.Current; Console.WriteLine("性别:"+id.Sex + " 姓名:"+id.Name); } //当然还可以输出HTML表格诸如此类的 }
但这不是万能的,例如我需要在迭代器中保留当前状态,而又需要重新遍历时,便无计可施了。
1 KeyValuePair<IDCard,IDCard> MakePair(IEnumerable<IDCard> enum) 2 { 3 var eor0 = enum.GetEnumerator(); 4 var eor1 = enum.GetEnumerator(); 5 while(true) 6 { 7 while(eor0.MoveNext()) 8 { 9 if(eor0.Current.Sex) 10 { 11 break; 12 } 13 } 14 while(eor0.MoveNext()) 15 { 16 if(!eor1.Current.Sex) 17 { 18 break; 19 } 20 } 21 if(xxxx)//配对成功 22 { 23 return new KeyValuePair<IDCard,IDCard>(eor0.Current,eor1.Current); 24 } 25 if(yyyy) 26 { 27 break; 28 } 29 } 30 }
好的,现在回来看看Java中的迭代器:
public interface Iterable<T> { //java.lang 1.5 Iterator<T> iterator(); } public interface Iterator<T> { //java.util 1.2 boolean hasNext(); T next(); void remove(); } //旧版本使用 public interface Enumeration<E> { //java.util 1.0 boolean hasMoreElements(); E nextElement(); }
在新版本的Iterator 和 旧版本的Enumeration比起来,相对的API缩短之外,还多了remove方法。
我。。。。不得不吐槽。。。。卧槽!!!怎么会多了个remove方法!!!!迭代器不是只负责迭代吗!?!为何要增加一个remove的API啊!!!这不是增加了修改原集合的权限么?!?那位Java达人能告诉我一下,他是怎么设计的?!?!原文最初的,在Java中不能使用迭代器实现了!!!
还有就是Java中List的设计,get 和set 是不是应该分开啊?返回一个接口只拥有只读权限的。(C#中也存在此问题,请教)
注:本文代码由于谈论了C#和Java,所以代码有点混乱,并且直接在文章里写,没经过运行测试,如果错误欢迎斧正。