c# java 迭代器 思考(2012

来源:互联网 发布:犀牛培训软件多少钱 编辑:程序博客网 时间:2024/05/16 09:38

很久之前便想动手写,无奈太懒。现在才开始动手,希望能尽快完成。
说起迭代器模式,不得不先说一下“镜像模式”(名字独创,不该称为模式)

所谓镜像模式,就是当函数返回引用对象时,理应返回该对象的拷贝,而不是直接返回该对象。
例如:

 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,所以代码有点混乱,并且直接在文章里写,没经过运行测试,如果错误欢迎斧正。

 


<script type="text/javascript"><!--google_ad_client = "ca-pub-1944176156128447";/* cnblogs 首页横幅 */google_ad_slot = "5419468456";google_ad_width = 728;google_ad_height = 90;//--></script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>