c# java 迭代器 思考(20121227 01:40)

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

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

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

原文地址:https://www.cnblogs.com/godzza/p/2841638.html