Java迭代器模式

来源:互联网 发布:大学生实用电脑软件 编辑:程序博客网 时间:2024/06/03 21:02

转自OLai的博客: Java中迭代器所引发的思考(List中迭代器的存在一直是使我迷茫的一个点)

  正像题目所说,我刚开始学Java接触到集合的时候,发现里面有个迭代器,不准确的说应该是我同学告诉我里面有个迭代器,然后说的很屌的样子。But,说实话,我真心觉得这个迭代器的存在简直是不可理解,比Java的泛型还扯淡,没有丝毫存在的价值。里面的迭代我完全可以自己写一个简单方法用来实习嘛,无非就遍历一下集合给我个这么复杂的方式干嘛。当时因为心思浮躁我就自欺欺人的告诉自己这属于一种设计模式,也就自己把自己蒙混过关不再考虑这个我丝毫不能理解的操作了。不过好在最近工作用的底层接口各种迭代器和C++模板搞得我想吐。也终于明白了迭代器真的是一个好的存在。


  首先咱们先不去分析Java中的迭代器的好坏,价值不大。Java集合中迭代器的存在真的是没什么太大的价值,不过这并不能怪Java本身,因为就我目前的知识而言,各个语言平台对迭代器的实现无非都是迭代一下集合中的元素,这也是我们在使用迭代器时不能明白他存在的价值的原因。那么来举个例子看一下,咱们来稍微感受一下迭代器的一个完美应用。
  现在呢,咱们来写一个斐波那契的实现。具体要求是:用计算机输出斐波那契数列的前count个元素。
  来一种最直观的的实现:

static void printFibonacci(int count) {        int fibonacci_n1 = 0;        int fibonacci_n2 = 1; // 由前两个值可以求得第三个值,所以定义两个变量用于缓存.        int fibonacci_n3 = 0; // 用于存储临时变量.        for (int i = 0; i < count; i++) {            System.out.println("fibonacci " + i + " :" + fibonacci_n2);            fibonacci_n3 = fibonacci_n1 + fibonacci_n2;            fibonacci_n1 = fibonacci_n2;            fibonacci_n2 = fibonacci_n3;        }    }

  这种实现,很简单很直接。但是这个函数除了针对本题目外其他毫无用处,因为该函数并不能将生成的斐波那契数列提供给其他函数使用。
  而我们要给其他函数使用可以将数据存入一个List列表,并作为返回值传递给外部函数使用。
  可是这样就完美了吗?NO!依然不完美,可以发现当count值比较小的时候是无所谓的,可是当这个值比较大的时候,List集合将会变得异常庞大,这在某些情况下是应该极力避免的情况(避免内存空间的过度浪费)。
  那么,来一个通过迭代器进行迭代计算的实现方案:
  具体思路是写一个迭代器,每次迭代时返回fibonacci数列中的下一个值。

public class Fibonacci {    public static void main(String[] args) {        // new一个迭代器对象        FibonacciIterator iterator = new FibonacciIterator();        int n = 30;        // 开始迭代        while (n-- > 0)            System.out.println(n + " : " + iterator.next());    }    // 定义用于生成Fibonacci数列的迭代器    static class FibonacciIterator implements Iterator<Integer> {        int fibonacci_n1 = 0;        int fibonacci_n2 = 1;        int fibonacci_n3 = 0;        @Override        public boolean hasNext() {            return true;        }        @Override        public Integer next() {            fibonacci_n3 = fibonacci_n1 + fibonacci_n2;            fibonacci_n1 = fibonacci_n2;            fibonacci_n2 = fibonacci_n3;            return fibonacci_n1;        }        @Override        public void remove() {        }    }}

  不知道我自作多情的继承了一个Java util包中的Iterator会不会叫大家更加迷惑,反正我是处于使它更像一个迭代器而这样做的。虽然它本身确实就是一个迭代器,只是并没有直接的和集合耦合在一起而已。
  可以发现在某些情况下需要一个fibonacci数列集合的时候,根本不需要耗费那么多内存去存取,可以通过迭代器的每次迭代去进行计算,将计算的时间摊薄在每一次调用中,而且除了程序代码以及两三个变量以外几乎不会再消耗任何内存空间。不知道大家有没有感受到我当时知道这个的时候所受到的惊吓,因为我一直是很嫌弃迭代器的,一个没有任何卵用的纯粹增加我编码复杂度的家伙。没有感受到的话看下面。
  当然圈重点,这个好玩又好用创意肯定不是我大脑灵光一闪突然想到的,因为我一直对迭代器这个东西持有偏见,只怪自己用得少啦。。。。。然后呢,为什么会幡然醒悟呢!因为这篇blog,上面的代码也是仿照这篇blog的Java代码的实现。

Python yield 使用浅析


清单 3. 通过 iterable 对象来迭代
for i in range(1000): pass
会导致生成一个 1000 个元素的 List,而代码:
for i in xrange(1000): pass
则不会生成一个 1000 个元素的 List,而是在每次迭代中返回下一个数值,内存空间占用很小。因为 xrange 不返回 List,而是返回一个 iterable 对象。


继续哦!!!!还有很多要说呢。
  于是这引起了我对迭代器的反思,以及在编码过程中对迭代器的特殊需求使我对迭代器有了更加深刻的理解。下面所说的或许你会感觉云里雾里,感觉自己好像是懂了可是又觉得没有懂。不要担心,你所需要的就是了解,知道有这个东西有这个方式就好,知道有这篇blog的存在就好。当你回头遇到一个比较复杂的需求,当你自己都觉得自己写的代码有点惨不忍睹的时候,当你去思考怎么可以把这部分代码变得更好的时候,加入的你自己所思考的解决方案恰好似乎感觉跟迭代器有点关联,然后你就可以再来翻翻看了,不过或许那时候你已经觉得不需要了,因为你买了关于设计模式的书了,上面说的要比我专业很多(飘过一个大写的尴尬)当然我写这篇blog之前专门把迭代器模式细细的翻了一遍,可是细化这篇blog内容的时候我觉得我是不是应该再翻下创建型模式哪一张(又一个大写的尴尬飘过)。好了,创建型下周再细细的翻,继续我的叨逼叨。


  为了节约大家的时间,我废话少说,长话短说,尽量简洁明了的给来家整明白喽(叫我一个话痨少说话,真的烦)。

1. 作用

迭代器呢,咱今天不说模式只说迭代器。首先我们必须明确一个前提,迭代器出现的目的是什么?迭代器一般会依赖于一个集合对象。一个集合对象应该提供出一种方法来让别人访问他的元素,而又不暴露他的内部结构。此外,针对不同的需要,还可能会以不同的方式来遍历这个列表。但是这就存在一个问题,即使可以预见到所有的遍历操作,你可能也不会希望列表的接口中充斥着各种不同的遍历操作。而且有时甚至会在一个列表上同时进行多个遍历操作,这样要在原始集合类中缓存多少状态位。

干脆举例子,什么都没例子来的快。
定义一个MyArrayList对象(继承ArrayList),add进去100个元素,现在我开始提我的遍历需求了。
  1. 我要顺序遍历。
  2. 我要逆序遍历。
  什么这些都好实现,直接给我在MyArrayList对象中添加next()、pre()方法就好了???
  3. 可以,那我现在要每间隔一个元素才返回一个元素。
  4. 那我现在要每间隔两个元素才返回一个元素。
  5. 那我现在要每间隔三个元素才返回一个元素 ……
  你加吧,看你能在MyArrayList中给我写多少方法,行就算你不嫌麻烦觉得依然可以维护。那我现在又有一个需求。
  6. 我要顺序遍历这个列表的同时还能逆序遍历等,同一时间我要使用三种、四种……遍历方案。
  这个时候你怎么实现,在MyArrayList中为每一种遍历方式定义一个状态值吗?累不累,真的不好管理这样。。。。。
  同时,迭代器也可以很方便的实现两个集合类的内容比较。

  那么迭代器的作用简而言之就是:帮助你将对列表的访问和遍历从列表对象中分离出来。将分离出来的方法还要状态值放入一个类中,这个类就是迭代器。

2. 迭代器的构成

  其实这个或许应该放在前面说。迭代器的构成呢,很简单,前面也有提到,迭代器往往依附于一个数据集合,就是用来操作一个聚合对象数据内容的。
  迭代器呢首先肯定存在一个获取当前元素的操作,迭代器中最重要的一个是,迭代器的遍历算法,这也是迭代器的心脏,Java中List派生类中对Iterator的遍历算法实现都是逐个遍历,也正是因为这样,使愚钝的我陷入了局限性思维,以为迭代器就仅仅是用来进行逐个遍历的,没有拓展到当出现其他遍历算法时使用迭代器的便捷性,更没意识到迭代器的真正效用是在我们程序员手里的时候,而不是在util包中的时候。心脏的作用就是泵血,有了算法这个心脏,迭代器还必须有一管子的数据集合作为血液。
  这就清晰明了了,迭代器由两部分组成:数据集合和迭代算法。

3. 迭代器的实现(感觉还是跑回设计模式了)

  基于以上迭代器结构的认识,我相信迭代器的实现对大家来说就很简单了。无非就是将集合数据给到迭代器,然后实现遍历算法即可。一个迭代器就这样实现了,可是为了代码的复用,以及代码结构的统一(这种统一方便我们对Java多态的使用。不知道这句话是不是很有问题,大家理解就好),在Java中我们往往不直接定义一个迭代器对象。而是定义一个迭代器接口,整个项目通过实现统一的迭代器接口来实现一个迭代器类。这样我们就可以很方便的实现两个集合数据的重合度比较了。至于怎么方便搭建可以自行看一下AbstractList的equals(Object o) 函数(我将其中的代码稍微简化了下)

public boolean equals(List list) {        ListIterator<Object > e1 = listIterator();        ListIterator<Object > e2 = list.listIterator();        while (e1.hasNext() && e2.hasNext()) {            Object  o1 = e1.next();            Object o2 = e2.next();            if (!(o1==null ? o2==null : o1.equals(o2)))                return false;        }        return !(e1.hasNext() || e2.hasNext());    }

  下面咱们根据迭代器结构进行分开讨论怎么构建一个迭代器(要实现余以及接口,或者继承自某个基类这个就不说了,对于继承大家用的说不定比我溜)。迭代器的结构中有两个东西,一个数据集合,一个迭代算法。
  首先从数据集合开始
  Iterator因为要对数据集合进行操作,所以Iterator中必须存在这个集合的引用(不是副本哦,副本的话数据变化你还得自己同步数据)。那要拿到这个集合的引用就有两种方式:
  1. 最直接的方式就是通过构造函数传入一个集合对象。嫌麻烦的话可以看第二种方式。
  2. 像Java中的集合类的实现那样,将Iterator定义到集合类内部,作为一个内部类实现,这样将自动拿到集合类对象的引用。
  再就是迭代算法了
  实现不同的迭代算法其实就是支持不同的操作,不同的对列表各个元素的操作。此时呢,也是有两种方式可供选择。
  1. 最直接的方式就是每一种迭代算法定义一个迭代器,可是这种方式在某些情况下就会很鸡肋,比如有两个集合类,这两个集合类的数据结构并不相同,可是又都需要一个相同迭代算法的迭代器实现,那样岂不是要为每一个类写一个相应的迭代器实现,这是很烦的。看第二种方法。
  2. 传方法,给迭代器传过去一个方法,该方法用于迭代算法的实现,可是Java中暂时并不支持直接传函数过去,只能通过定义一个通用接口对象传递给Iterator进行调用(比如说自己定义一个Iteratable接口,里面有一个Iterator_algorithm方法,然后我只需要在Iterator中的响应方法中调用Iterator_algorithm方法即可)。这种方法虽然看起来蛮不错,可是要设计的好也不容易,需要根据项目的具体需求去设计改进。设计Iteratable接口的准则应该是,在Iteratable中不进行变量的定义,不进行复杂参数的传递,总之一句话Iteratable接口的实现尽量只用于迭代算法的计算逻辑实现,不要与数据集合产生任何耦合
。如果逼不得已必须会产生耦合那就需要根据具体项目进行具体分析采用什么方式了。
  为什么我会很欣赏第二种方式,因为它足够简单,通过第二种方式,你不需要写迭代的具体方式,而只需要交给上层使用该迭代器的人员对迭代算法进行实现。极大地避免了因方法实现而产生的摩擦。当然你或许会说通过方式一也一样可以交给外层人员,yes,可是这个时候外层人员新建了一个Iterator,那么就不能通过很方便的内部类来实现Iterator了,而是要在创建Iterator对象的时候将List集合对象作为参数传入。当然外部人员还可以采用将新实现的Iterator类的class对象传递给集合对象,然后在集合对象内部通过反射进行实现,这样代码看起来依然像Java的结构一样,可是你不嫌麻烦的话怎么来都行。
  各个语言平台虽然有很大差异,但是都不会妨碍你的任何一个需求的实现,只是在有些语言平台上你的需求实现过程会绕很多弯弯,在有些平台则会很顺利很简单。同样的在同一个语言平台同一个需求也往往会有很多种不同的解决方案,这些方案或许相比于其他平台都很复杂,又或者有些简单有些复杂,具体怎么实现不能一概而论,需要根据项目实况进行分析,选择最通用、最便捷好用的方式进行就行。

  原谅我后面没有贴代码进行更好的讲解,因为你真的会自己明白的。还有学习任何东西都不要慌,时间线拉的要长一点,慌是慌不来的。切莫急功急利,主要提醒我自己。

原创粉丝点击