Heim  >  Artikel  >  Java  >  Detaillierte Codebeispiele von Java-Iteratoren

Detaillierte Codebeispiele von Java-Iteratoren

黄舟
黄舟Original
2017-03-14 11:58:463708Durchsuche


1. Zusammenfassung

 Das Iteratormuster ist symbiotisch mit Sammlungen. Im Allgemeinen müssen wir, solange wir einen Container implementieren, den Iterator des Containers bereitstellen. Der Vorteil der Verwendung von Iteratoren besteht darin, dass sie die internen Implementierungsdetails des Containers kapseln, eine einheitliche Durchlaufmethode für verschiedene Sammlungen bereitstellen und den Clientzugriff und die Erfassung von Daten im Container vereinfachen. Auf dieser Basis können wir Iterator verwenden, um das Durchlaufen der Sammlung abzuschließen. Darüber hinaus können auch die Syntax for loop und foreach zum Durchlaufen verwendet werden Sammelklasse. ListIterator ist ein bidirektionaler Iterator, der einzigartig für die List-Containerfamilie ist. Zu den Hauptpunkten dieses Artikels gehören:

  • Iteratormuster

  • Iterator-Iterator und iterierbare Schnittstelle

  • Schleifendurchlauf: Ähnlichkeiten und Unterschiede von foreach, Iterator und for

  • Kurze Beschreibung von ListIterator (detaillierte Erläuterung der Containerliste)


2. Iteratormuster

 Das Iteratormuster ist symbiotisch mit Sammlungen. Solange wir einen Container implementieren, müssen wir im Allgemeinen auch den Iterator des Containers bereitstellen, genau wie Collection (Liste, Set usw.) in Java Diese Container haben ihren eigenen Iterator. Wenn wir einen neuen Container implementieren möchten, müssen wir natürlich auch das Iteratormuster einführen und einen Iterator für unseren Container implementieren. Die Vorteile der Verwendung von Iteratoren sind: kapselt die internen Implementierungsdetails des Containers. Es kann eine einheitliche Durchlaufmethode für verschiedene Sammlungen bereitstellen, wodurch der Clientzugriff vereinfacht und Daten im Container abgerufen werden.

Da Container und Iteratoren jedoch so eng miteinander verbunden sind, stellen die meisten Sprachen bei der Implementierung von Containern auch entsprechende Iteratoren bereit, und in den meisten Fällen Die von diesen Sprachen bereitgestellten Container und Iteratoren kann unsere Bedürfnisse erfüllen. Daher ist es in der Realität relativ selten, dass wir das Iteratormuster selbst implementieren müssen. Oft müssen wir nur vorhandene Container und Iteratoren in der Sprache verwenden.


1. Definition und Struktur

  • Definition

    Iterator-Modus, auch Cursor genannt ( Cursor )-Modus. Die von GOF gegebene Definition lautet: stellt eine Methode bereit, um auf jedes Element in einem Container (Container) Objekt zuzugreifen, ohne die internen Details des Containerobjekts offenzulegen.

    Aus der Definition ergibt sich das Iteratormuster für Container. Wir wissen, dass der Zugriff auf Containerobjekte Traversal-Algorithmen beinhalten muss. Sie können die Traversal-Methoden einfach in das Containerobjekt einbinden oder überhaupt keinen Traversal-Algorithmus bereitstellen und die Leute, die den Container verwenden, ihn selbst implementieren lassen. Beide Fälle scheinen das Problem zu lösen. Im ersteren Fall trägt der Container jedoch zu viele Funktionen. Er ist nicht nur für die Verwaltung von Elementen innerhalb seines eigenen „Containers“ verantwortlich (Hinzufügen, Löschen, Ändern, Überprüfen usw.), sondern stellt auch eine Schnittstelle zum Durchlaufen bereit selbst; und schließlich Das Wichtigste ist, dass aufgrund des Problems, den Durchlauf--Status zu speichern, nicht mehrere Durchläufe gleichzeitig für dasselbe Containerobjekt durchgeführt werden können, und das Reset-Vorgang muss hinzugefügt werden . Die zweite Methode erspart Ärger, legt aber die internen Details des Containers offen.


  • Charakterzusammensetzung im Iteratormodus

      Iteratorrolle (Iterator): Die Iteratorrolle ist für die Definition der Schnittstelle für den Zugriff auf und das Durchqueren von Elementen verantwortlich  

    Konkrete Iteratorrolle (Konkreter Iterator): Konkrete Iteratorrolle Um die Iteratorschnittstelle zu implementieren und um den Durchlauf aufzuzeichnen Position von ; Schnittstelle;

     Betoncontainer: BetoncontainerrolleImpuliert die Schnittstelle zum Erstellen einer konkreten Iteratorrolle - dies Die spezifische Iteratorrolle hängt mit der Struktur dieses Containers zusammen.


  • Strukturdiagramm
       Detaillierte Codebeispiele von Java-Iteratoren

    Wie aus der Struktur ersichtlich ist, Das Iteratormuster fügt die Iteratorrolle zwischen dem Client und dem Container hinzu. Durch das Hinzufügen der Iteratorrolle kann die Offenlegung interner Details des Containers wirksam vermieden werden, und das Design entspricht außerdem dem Prinzip der Einzelverantwortung.
     
     Es ist wichtig zu beachten, dass im Iteratormuster die spezifische Iteratorrolle und die spezifische Containerrolle miteinander gekoppelt sind — — Der Traversalalgorithmus hängt eng mit den internen Details des Containers zusammen. Um das Client-Programm vom Dilemma der Kopplung mit der spezifischen Iteratorrolle zu befreien und die Änderungen zu vermeiden, die das Client-Programm durch den Ersatz der spezifischen Iteratorrolle mit sich bringt, abstrahiert das Iteratormuster die spezifische Iteratorrolle und macht das Client-Programm allgemeiner und Wiederverwendbarkeit, dies wird als polymorphe Iteration bezeichnet.


  • Anwendbarkeit

    1. Zugriff auf den Inhalt eines Containerobjekts, ohne dessen interne Darstellung offenzulegen

     2. Unterstützt mehrere Durchläufe von Containerobjekten

     3. Bietet eine einheitliche Schnittstelle zum Durchqueren verschiedener Containerstrukturen (d. h. unterstützt polymorphe Iteration).


2. Beispiele

Da die Vorschriften des Iteratormusters selbst relativ locker sind, sind die spezifischen Implementierungen unterschiedlich hier Nur um ein Beispiel zu nennen. Bevor wir ein Beispiel geben, wollen wir zunächst aufzählen, wie das Iteratormuster implementiert wird.

  • Die Iteratorrolle definiert die Schnittstelle für die Durchquerung, gibt jedoch nicht an, wer die Iteration steuert. Im Java Collection Framework steuert das Client-Programm den Traversalprozess, der als externer Iterator bezeichnet wird um die Iteration durch den Iterator selbst zu steuern, der als interner Iterator bezeichnet wird. Externe Iteratoren sind flexibler und leistungsfähiger als interne Iteratoren, und die Benutzerfreundlichkeit interner Iteratoren in der Java-Sprachumgebung ist im Iterator nicht verfügbar Muster Die Angabe, wer den Traversalalgorithmus implementieren soll, scheint in der Iteratorrolle eine Selbstverständlichkeit zu sein.

  • Weil es praktisch ist, verschiedene Durchlaufalgorithmen für einen Container zu verwenden, und es auch bequem ist, einen Durchlaufalgorithmus auf verschiedene Container anzuwenden. Dies zerstört jedoch die Kapselung des Containers – die Containerrolle muss ihre eigenen privaten
  • Attribute

    offenlegen, was in Java bedeutet, dass sie ihre eigenen privaten Attribute anderen Klassen offenlegen  Dann sagen wir mal Es wird in die Containerrolle verschoben, um es zu implementieren. Auf diese Weise wird die Iteratorrolle erhöht, um nur eine Funktion zum Durchlaufen der aktuellen Position zu speichern. Der Traversalalgorithmus ist jedoch eng an einen bestimmten Container gebunden. Im Java Collection-Framework ist die spezifische Iteratorrolle die in der Containerrolle definierte interne Klasse

    , wodurch die Kapselung des Containers geschützt wird. Gleichzeitig bietet der Container aber auch eine Schnittstelle für einen Traversalalgorithmus, und Sie können Ihren eigenen Iterator erweitern.

    Werfen wir einen Blick auf die Implementierung von Iteratoren in der Java Collection:

Iteration Die Verwendung des Iteratormusters
//迭代器角色,仅仅定义了遍历接口public interface Iterator<E> {
    boolean hasNext();
    E next();    void remove();
}//容器角色,这里以 List 为例,间接实现了 Iterable 接口public interface Collection<E> extends Iterable<E> {
    ...
    Iterator<E> iterator();
    ...
}
public interface List<E> extends Collection<E> {}
//具体容器角色,便是实现了 List 接口的 ArrayList 等类。为了突出重点这里指罗列和迭代器相关的内容
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {…… 
//这个便是负责创建具体迭代器角色的工厂方法public Iterator<E> iterator() {
 return new Itr();
}
//具体迭代器角色,它是以内部类的形式出来的。 AbstractList 是为了将各个具体容器角色的公共部分提取出来而存在的。
//作为内部类的具体迭代器角色
private class Itr implements Iterator<E> { 
int cursor = 0;
 int lastRet = -1;  
 //集合迭代中的一种“快速失败”机制,这种机制提供迭代过程中集合的安全性. ArrayList 中存在 modCount 属性,增删操作都会使 modCount++,
 //通过两者的对比,迭代器可以快速的知道迭代过程中是否存在 list.add() 类似的操作,存在的话快速失败! int expectedModCount = modCount;  
 
 public boolean hasNext() {
  return cursor != size();
 }

 public Object next() {
  checkForComodification();   
  //快速失败机制  try {
   Object next = get(cursor);
   lastRet = cursor++;
   return next;
  } catch(IndexOutOfBoundsException e) {
   checkForComodification();   
   //快速失败机制   
   throw new NoSuchElementException();
  }
 }

 public void 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();   
   //抛出异常,迭代终止 
   }
}

    Das Clientprogramm muss zuerst die spezifische Containerrolle und dann die spezifische Iteratorrolle über die spezifische Containerrolle erhalten. Auf diese Weise können bestimmte Iteratorrollen zum Durchlaufen des Containers verwendet werden...
  • 3. Anwendbare Situationen

    Wir können die Anwendung des Iterators sehen Muster zu Containern Bringt die folgenden Vorteile:
 1)
Unterstützt das Durchlaufen einer Containerrolle auf unterschiedliche Weise. Der Effekt ist je nach Implementierung unterschiedlich (z. B. Iterator und ListIterator in List).

 2)

Die Containerschnittstelle wurde vereinfacht.

Um die Skalierbarkeit in der Java Collection zu verbessern, bietet der Container jedoch weiterhin eine Traversal-Schnittstelle.

  3) 简化了遍历方式。对于对象集合的遍历,还是比较麻烦的,对于数组或者有序列表,我们尚可以通过游标来取得,但用户需要在对集合了解很清楚的前提下,自行遍历对象,但是对于 哈希表 来说,用户遍历起来就比较麻烦了。而引入了迭代器方法后,用户用起来就简单的多了。

  4) 可以提供多种遍历方式。比如,对于有序列表,我们可以根据需要提供正序遍历,倒序遍历两种迭代器,用户用起来只需要得到我们实现好的迭代器,就可以方便的对集合进行遍历了。

  5) 对同一个容器对象,可以同时进行多个遍历。因为遍历状态是保存在每一个迭代器对象中的。

  6) 封装性良好,用户只需要得到迭代器就可以遍历,而对于遍历算法则不用去关心。

  7) 在 Java Collection 中,迭代器提供一种快速失败机制 ( ArrayList是线程不安全的,在ArrayList类创建迭代器之后,除非通过迭代器自身remove或add对列表结构进行修改,否则在其他线程中以任何形式对列表进行修改,迭代器马上会抛出异常,快速失败),防止多线程下迭代的不安全操作。


 由此,也可以得出迭代器模式的适用范围:

  1) 访问一个容器对象的内容而无需暴露它的内部表示;

  2) 支持对容器对象的多种遍历;

  3) 为遍历不同的容器结构提供一个统一的接口(多态迭代)


三、Iterator 迭代器与 Iterable 接口

1、Iterator 迭代器接口 : java.util 包

  Java 提供一个专门的迭代器接口 Iterator,我们可以对某个容器实现该 Interface,来提供标准的 Java 迭代器。


  • 用 Iterator 模式实现遍历集合

      Iterator 模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。

      例如,如果没有使用 Iterator,遍历一个数组 的方法是使用索引

for(int i=0; i<array.size(); i++) { ... get(i) ... }

  而 遍历一个HashSet必须使用 while 循环或 foreach,但不能使用for循环

while((e=e.next())!=null) { ... e.data() ... }

  对以上两种方法,客户端都必须事先知道集合的类型(内部结构),访问代码和集合本身是紧耦合的,无法将访问逻辑从集合类和客户端代码中分离出来,从而导致每一种集合对应一种遍历方法,客户端代码无法复用。更恐怖的是,如果以后需要把 ArrayList 更换为 LinkedList,则原来的客户端代码必须全部重写。

  为解决以上问题,Iterator模式总是用同一种逻辑来遍历集合:

<p style="margin-bottom: 7px;">for(Iterator it = c.iterater(); it.hasNext(); ) { ... } <br/></p>

  奥秘在于 客户端自身不维护遍历集合的”指针”,所有的内部状态(如当前元素位置,是否有下一个元素)都由 Iterator 来维护,而这个 Iterator 由集合类通过工厂方法生成,因此,它知道如何遍历整个集合。而且,客户端从不直接和集合类打交道,它总是控制Iterator,向它发送”向前”,”向后”,”取当前元素”的指令,就可以间接遍历整个集合。

  首先看看 java.util.Iterator 接口的定义:

public interface Iterator {
    boolean hasNext(); 
    Object next(); 
    void remove(); // 可选操作 }

  依赖前两个方法就能完成遍历,典型的代码如下:

for(Iterator it = c.iterator(); it.hasNext(); ) { Object o = it.next(); // 对o的操作... }

  多态迭代 : 每一种集合类返回的 Iterator 具体类型可能不同,Array 可能返回 ArrayIterator,Set 可能返回 SetIterator,Tree 可能返回 TreeIterator,但是它们都实现了 Iterator 接口,因此,客户端不关心到底是哪种 Iterator,它只需要获得这个 Iterator 接口即可,这就是面向对象的威力。


2、Iterable 接口 : java.lang

  Java 中还提供了一个 Iterable 接口,Iterable接口实现后的功能是“返回”一个迭代器 。我们常用的实现了该接口的子接口有: Collection系列,包括 List, Queue, Set 在内。特别值得一提的是,Map 接口没有实现 Iterable 接口。该接口的 iterator() 方法返回一个标准的 Iterator 实现。


  • 实现 Iterable 接口来实现适用于 foreach 遍历的自定义类

      Iterable 接口包含一个能够产生 Iterator 的 iterator() 方法,并且 Iterable 接口被 foreach 用来在序列中实现移动。因此,实现这个接口允许对象成为 foreach 语句的目标,也就可以通过 foreach语法遍历你的底层序列。

      在 JDK1.5 以前,用 Iterator 遍历序列的语法:

for(Iterator it = c.iterator(); it.hasNext(); ) { Object o = it.next(); // 对o的操作... }

  在 JDK1.5 以及以后的版本中,引进了 foreach,对上面的代码在语法上作了简化 ( 但是限于只读,如果需要remove,还是直接使用 Iterator )

for(Type t : collection) { ... }

3、思辨

  • 为什么一定要去实现 Iterable 这个接口呢? 为什么不直接实现 Iterator接口 呢?

      看一下 JDK 中的集合类,比如 List一族或者Set一族,都是实现了 Iterable 接口,但并不直接实现 Iterator 接口。仔细想一下这么做是有道理的:因为 Iterator接口的核心方法 next() 或者 hasNext() 是依赖于迭代器的当前迭代位置的。若 Collection 直接实现 Iterator 接口,势必导致集合对象中包含当前迭代位置的数据(指针)。当集合在不同方法间被传递时,由于当前迭代位置不可预置,那么 next() 方法的结果会变成不可预知。除非再为 Iterator接口 添加一个 reset() 方法,用来重置当前迭代位置。但即使这样,Collection 也只能同时存在一个当前迭代位置(不能同时多次迭代同一个序列:必须要等到当前次迭代完成并reset后,才能再一次从头迭代)。 而选择实现 Iterable 接口则不然,每次调用都会返回一个从头开始计数的迭代器(Iterator),因此,多个迭代器间是互不干扰的。


四、foreach,Iterator,for

  • foreach 和 Iterator 的关系

      foreach 是 jdk5.0 新增加的一个循环结构,可以用来处理集合中的每个元素而不用考虑集合的下标。

格式如下 :

 for(variable:collection){ statement; }

   定义一个变量用于暂存集合中的每一个元素,并执行相应的语句(块)。Collection 必须是一个数组或者是一个实现了 lterable 接口的类对象。

   可以看出,使用 foreach 循环语句的优势在于更加简洁,更不容易出错,不必关心下标的起始值和终止值。forEach 不是关键字,关键字还是 for ,语句是由 iterator 实现的,它们最大的不同之处就在于 remove() 方法上。

   特别地,一般调用删除和添加方法都是具体集合的方法,例如:

List list = new ArrayList(); 
list.add(...); 
list.remove(...);
...

  但是,如果在循环的过程中调用集合的 remove() 方法,就会导致循环出错,因为循环过程中 list.size() 的大小变化了,就导致了错误(Iterator的快速失败机制)。 所以,如果想在循环语句中删除集合中的某个元素,就要用迭代器 iterator 的 remove() 方法,因为它的 remove() 方法不仅会删除元素,还会维护一个标志,用来记录目前是不是可删除状态,例如,你不能连续两次调用它的remove()方法,调用之前至少有一次 next() 方法的调用。因此,foreach 就是为了让用 iterator 循环访问的形式简单,写起来更方便。当然功能不太全,所以若是需要使用删除操作,那么还是要用它原来的形式。


  • 使用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无序,结构不同,他们的访问算法也不一样.


五、ListIterator 简述

1、简述

   ListIterator 系列表迭代器,实现了Iterator接口。该迭代器允许程序员按任一方向遍历列表、迭代期间修改列表,并获得迭代器在列表中的当前位置。ListIterator 没有当前元素;它的光标位置始终位于调用 previous() 所返回的元素和调用 next() 所返回的元素之间。长度为 n 的列表的迭代器有 n+1 个可能的指针位置,如下面的插入符举例说明:

          Detaillierte Codebeispiele von Java-Iteratoren

   注意,remove() 和 set(Object) 方法不是根据光标位置定义的;它们是根据对调用 next() 或 previous() 所返回的最后一个元素的操作定义的。


2、与 Iterator 区别

   Iterator 和 ListIterator 主要区别有:

  • ListIterator 有 add()方法,可以向 List 中添加对象,而 Iterator 不能 ;

  • ListIterator 和 Iterator 都有 hasNext()和next()方法,可以实现顺序向后遍历。但是 ListIterator 有 hasPrevious() 和 previous() 方法,可以实现逆向(顺序向前)遍历,而 Iterator 就不可以 ;

  • ListIterator 可以利用 nextIndex() 和 previousIndex() 定位当前的索引位置,而 Iterator 没有此功能 ;

  • ListIterator 可以通过 listIterator() 方法和 listIterator(int index) 方法获得,而 Iterator 只能由 iterator() 方法获得 ;

  • 二者都可以实现删除对象,但是ListIterator可以使用set()方法实现对象的修改。Iterator 仅能遍历,不能修改。因为ListIterator的这些功能,可以实现对LinkedList, ArrayList等List数据结构的操作。

Das obige ist der detaillierte Inhalt vonDetaillierte Codebeispiele von Java-Iteratoren. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn