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

  • 相关阅读:
    w3cschoolDjango中文教程
    Golang基本语法2
    w3cschool微信小程序开发文档服务端
    bianchenggo语言概要总结
    Golang语言简介1
    Spring中的@Transactional注解为什么要加rollbackFor = Exception.class之源码解析
    Java 编译期与运行期,别傻傻分不清楚!
    java为什么有些异常throw出去需要在函数头用throws声明,一些就不用?
    @Transactional(rollbackFor=Exception.class)的使用
    Lambda 表达式有何用处?如何使用?
  • 原文地址:https://www.cnblogs.com/godzza/p/2841638.html
Copyright © 2020-2023  润新知