JAVA迭代器与迭代模式
一、介绍
在jdk中,与迭代器相关的接口有两个:Iterator 与 Iterable
Iterator:迭代器,Iterator及其子类通常是迭代器本身的结构与方法;迭代器是一种模式,它可以使得对于序列类型的数据结构的遍历行为与被遍历的对象分离,即我们无需关心该序列的底层结构是什么样子的。只要拿到这个对象,使用迭代器就可以遍历这个对象的内部.
Iterable:可迭代的,那些想用到迭代器功能的其它类,如AbstractList HashMap等,需要实现该接口。
1、Iterator
Java提供一个专门的迭代器<<interface>>Iterator,我们可以对某个序列实现该interface,来提供标准的Java迭代器。Iterator接口实现后的功能是“使用”一个迭代器。
Package java.util; public interface Iterator<E> { boolean hasNext();//判断是否存在下一个对象元素 E next(); void remove(); }
2、Iterable
Java中还提供了一个Iterable接口,Iterable接口实现后的功能是“返回”一个迭代器,我们常用的实现了该接口的子接口有: Collection<E>, Deque<E>, List<E>, Queue<E>, Set<E> 等.该接口的iterator()方法返回一个标准的Iterator实现。实现这个接口允许对象成为 Foreach 语句的目标。就可以通过Foreach语法遍历你的底层序列。
Iterable接口包含一个能够产生Iterator的iterator()方法,并且Iterable接口被foreach用来在序列中移动。因此如果创建了任何实现Iterable接口的类,都可以将它用于foreach语句中。
Iterator对集合类中的任何一个实现类,都可以返回这样一个Iterator对象。可以适用于任何一个类。
因为集合类(List和Set等)可以装入的对象的类型是不确定的,从集合中取出时都是Object类型,用时都需要进行强制转化,这样会很麻烦,用上泛型,就是提前告诉集合确定要装入集合的类型,这样就可以直接使用而不用显示类型转换,非常方便。
4、foreach和Iterator的关系
for each是jdk5.0新增加的一个循环结构,可以用来处理集合中的每个元素而不用考虑集合定下标。
格式如下
for(variable:collection){ statement; }
定义一个变量用于暂存集合中的每一个元素,并执行相应的语句(块)。foreach的目标对象collection必须是一个数组或者是一个实现了lterable接口的类对象。
forEach就是为了让用iterator循环访问的形式简单,写起来更方便。当然功能不太全,所以但如有删除操作,还是要用它原来的形式。
5、使用for循环与使用迭代器iterator的对比
效率上的各有有事
采用ArrayList对随机访问比较快,而for循环中的get()方法,采用的即是随机访问的方法,因此在ArrayList里,for循环较快
采用LinkedList则是顺序访问比较快,iterator中的next()方法,采用的即是顺序访问的方法,因此在LinkedList里,使用iterator较快
从数据结构角度分析,for循环适合访问顺序结构,可以根据下标快速获取指定元素.而Iterator 适合访问链式结构,因为迭代器是通过next()和Pre()来定位的.可以访问没有顺序的集合.
而使用 Iterator 的好处在于可以使用相同方式去遍历集合中元素,而不用考虑集合类的内部实现(只要它实现了 java.lang.Iterable 接口),如果使用 Iterator 来遍历集合中元素,一旦不再使用 List 转而使用 Set 来组织数据,那遍历元素的代码不用做任何修改,如果使用 for 来遍历,那所有遍历此集合的算法都得做相应调整,因为List有序,Set无序,结构不同,他们的访问算法也不一样.
5、迭代器的实现
看完源码,我们来看看迭代器是如何使用的:
1. 若类A想要使用迭代器,则它的类声明部分为 class A implement Iterable
2. 在类A实现中,要实现Iterable接口中的唯一方法:Iterator<T> iterator(); 这个方法用于返回一个迭代器,即Iterator接口及其子类;
3. 在类A中,定义一个内部类S,专门用于实现Iterator接口,定制类A自已的迭代器实现。
如下:
我们再来看看jdk中是如何来使用的,如抽象类AbstractList:
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { // List接口实现了Collection<E>, Iterable<E> protected AbstractList() { } ... public Iterator<E> iterator() { return new Itr(); // 这里返回一个迭代器 } private class Itr implements Iterator<E> { // 内部类Itr实现迭代器 int cursor = 0; int lastRet = -1; int expectedModCount = modCount; public boolean hasNext() { // 实现hasNext方法 return cursor != size(); } public E next() { // 实现next方法 checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public void remove() { // 实现remove方法 if (lastRet == -1) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } } }
示例:
package com.dxz.iterator; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; class ScanAppleStore implements Iterable<String> { ArrayList<String> appleStore = new ArrayList<String>(); ScanAppleStore() { Collections.addAll(appleStore, "Sweet", "Sour", "Bitter", "litter Sweet", "litter Sour", "litter Bitter"); System.out.print(appleStore); } @Override public Iterator<String> iterator() { return new Iterator<String>() { private int i = 0; public boolean hasNext() { if (i < appleStore.size()) { return true; } else { return false; } } public String next() { return appleStore.get(i++); } public void remove() { System.out.print("not defined!"); } }; } public Iterable<String> reverseIterator() { return new Iterable<String>() { public Iterator<String> iterator() { return new Iterator<String>() { private int i = appleStore.size() - 1; public boolean hasNext() { if (i > -1) { return true; } else { return false; } } public String next() { return appleStore.get(i--); } public void remove() { System.out.print("not defined!"); } }; } }; } }
测试:
package com.dxz.iterator; public class TestIterable { TestIterable() { ScanAppleStore appleTree = new ScanAppleStore(); System.out.println("Try normal iterator:"); for (String str : appleTree) { System.out.println(str); } System.out.println("======================"); System.out.println("Try reverse iterator:"); for (String str : appleTree.reverseIterator()) { System.out.println(str); } } public static void main(String[] args) { TestIterable a = new TestIterable(); } }
结果:
[Sweet, Sour, Bitter, litter Sweet, litter Sour, litter Bitter]Try normal iterator: Sweet Sour Bitter litter Sweet litter Sour litter Bitter ====================== Try reverse iterator: litter Bitter litter Sour litter Sweet Bitter Sour Sweet
6、迭代器模式:
6.1、提出问题:什么是迭代器模式? 为什么会有迭代器模式?
GOF给出的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。
我的理解: 就是把遍历算法从容器对象中独立出来,为什么要把遍历算从容器对象中独立出来呢? 因为在面向对象设计中,一个难点就是辨认对象的职责。理想的状态下,一个类应该只有一个单一的职责。职责分离可以最大限度的去耦合,但是职责单一说起来容易,做起来难。具体到本模式,我们明显可以看到,一个容器对象它提供了两个职责:一是组织管理数据对象,二是提供遍历算法。
所以:Iterator模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明的访问集合内部的数据。
6.2、下面开始讲迭代器模式:
迭代器模式由以下角色组成:
1) 迭代器角色(Iterator):迭代器角色负责定义访问和遍历元素的接口。
2) 具体迭代器角色(Concrete Iterator):具体迭代器角色要实现迭代器接口,并要记录遍历中的当前位置。
3) 容器角色(Container):容器角色负责提供创建具体迭代器角色的接口。
4) 具体容器角色(Concrete Container):具体容器角色实现创建具体迭代器角色的接口——这个具体迭代器角色于该容器的结构相关。
迭代器模式的类图如下:
package com.dxz.pattern.iterator; public interface Iterator { public Object next(); public boolean hasNext(); } package com.dxz.pattern.iterator; import java.util.ArrayList; import java.util.List; public class ConcreteIterator implements Iterator { private List list = new ArrayList(); private int cursor = 0; public ConcreteIterator(List list) { this.list = list; } @Override public Object next() { Object obj = null; if (this.hasNext()) { obj = this.list.get(cursor++); } return obj; } @Override public boolean hasNext() { if (cursor == list.size()) { return false; } return true; } } package com.dxz.pattern.iterator; public interface Aggregate { public void add(Object obj); public void remove(Object obj); public Iterator iterator(); } package com.dxz.pattern.iterator; import java.util.ArrayList; import java.util.List; public class ConcreteAggregate implements Aggregate { private List list = new ArrayList(); @Override public void add(Object obj) { list.add(obj); } @Override public void remove(Object obj) { list.remove(obj); } @Override public Iterator iterator() { return new ConcreteIterator(list); } } package com.dxz.pattern.iterator; public class Client { public static void main(String[] args) { Aggregate ag = new ConcreteAggregate(); ag.add("小明"); ag.add("小红"); ag.add("小刚"); Iterator it = ag.iterator(); while (it.hasNext()) { String str = (String) it.next(); System.out.println(str); } } }
结果:
小明
小红
小刚
优点:
1、它支持以不同的方式遍历一个聚合对象。
2、迭代器简化了聚合类。
3、在同一个聚合上可以有多个遍历。
4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
使用场景:
1、访问一个聚合对象的内容而无须暴露它的内部表示。
2、需要为聚合对象提供多种遍历方式。
3、为遍历不同的聚合结构提供一个统一的接口。
注意事项:Iterator模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明的访问集合内部的数据。
二、JDK中的集合迭代设计
看上面的Iterator与Iterable接口源码,从以上的定义中可以发现,似乎Iterable()接口和Iterator()接口完全一致,没有任何区别。可以发现这又是一个支持程序多样化的巧妙设计,充分的支持了多态和解耦。见上面的6.1说明。
JDK中的大多数集合都是拓展了Collection接口的,而Collection接口拓展了接口Iterable,根据以上的对Iterable接口的定义可以发现,其要求实现其的类都提供一个返回迭代器Iterator<T>对象的方法。
上述类图中,实线边框的是实现类,比如ArrayList,LinkedList,HashMap等,折线边框的是抽象类,比如AbstractCollection,AbstractList,AbstractMap等,而点线边框的是接口,比如Collection,Iterator,List等。
再看集合的两大根接口Collection与Map的源码,发现:
发现一个特点,上述所有的集合类,都依赖了Iterator接口,这是一个用于遍历集合中元素的接口,而在Iterator接口中主要包含hashNext(),next(),remove()三种方法。它的一个子接口LinkedIterator在它的基础上又添加了三种方法,分别是add(),previous(),hasPrevious()。也就是说如果是先Iterator接口,那么在遍历集合中元素的时候,只能往后遍历,被遍历后的元素不会在遍历到,通常无序集合实现的都是这个接口,比如HashSet,HashMap;而那些元素有序的集合,实现的一般都是LinkedIterator接口,实现这个接口的集合可以双向遍历,既可以通过next()访问下一个元素,又可以通过previous()访问前一个元素,比如ArrayList。
还有一个特点就是抽象类的使用。如果要自己实现一个集合类,去实现那些抽象的接口会非常麻烦,工作量很大。这个时候就可以使用抽象类,这些抽象类中给我们提供了许多现成的实现,我们只需要根据自己的需求重写一些方法或者添加一些方法就可以实现自己需要的集合类,工作流昂大大降低。
根据上面的特征,可以总结集合类的遍历方法有:
Collection类集合:
package com.dxz.iterator; import java.util.ArrayList; public class IteratorCollectionObjectDemo2 { public static void main(String[] args) { ArrayList<String> list = new ArrayList<String>(); list.add("1"); list.add("3"); list.add("5"); list.add("2"); System.out.println("1.用for循环遍历:只能遍历数组,队列"); // 1.用for循环遍历:只能遍历数组,队列 // 优点:方便,实用,按顺序取出元素 // 缺点:只能根据下标取出元素 for (int i = 0; i < list.size(); i++) { String str = list.get(i); // 获取队列中的元素 System.out.print(str+","); // 输出元素 } System.out.println("\n2.用迭代器遍历:可遍历数组、队列、集合、映射示例:"); // 2.用迭代器遍历:可遍历数组、队列、集合、映射示例: // 优点:可以遍历的数据类型的种类多 // 缺点:难理解 java.util.Iterator<String> iter = list.iterator(); // 判断迭代器中是否有元素 while (iter.hasNext()) { String str = iter.next(); // 如果有,就取出一个 System.out.print(str+","); // 输出元素 } System.out.println("\n3.用加强for循环:可遍历数组、队列、集合、映射遍历队列:"); // 3.用加强for循环:可遍历数组、队列、集合、映射遍历队列: for (String str : list) { System.out.print(str+","); // 输出元素 } } }
结果:
1.用for循环遍历:只能遍历数组,队列 1,3,5,2, 2.用迭代器遍历:可遍历数组、队列、集合、映射示例: 1,3,5,2, 3.用加强for循环:可遍历数组、队列、集合、映射遍历队列: 1,3,5,2,
Map类集合:
package com.dxz.iterator; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class IteratorCollectionObjectDemo { public static void main(String[] args) { Map<String, String> map = new HashMap<String, String>(); map.put("1", "value1"); map.put("2", "value2"); map.put("3", "value3"); // 第一种:普遍使用,二次取值 System.out.println("通过Map.keySet遍历key和value:"); for (String key : map.keySet()) { System.out.println("key= " + key + " and value= " + map.get(key)); } // 第二种 System.out.println("通过Map.entrySet使用iterator遍历key和value:"); Iterator<Map.Entry<String, String>> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, String> entry = it.next(); System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue()); } // 第三种:推荐,尤其是容量大时 System.out.println("通过Map.entrySet遍历key和value"); for (Map.Entry<String, String> entry : map.entrySet()) { System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue()); } // 第四种 System.out.println("通过Map.values()遍历所有的value,但不能遍历key"); for (String v : map.values()) { System.out.println("value= " + v); } } }