>  기사  >  Java  >  Java 반복자의 자세한 코드 예

Java 반복자의 자세한 코드 예

黄舟
黄舟원래의
2017-03-14 11:58:463707검색


1. 요약

 반복자 패턴 은 컬렉션과 공생합니다. 일반적으로 컨테이너를 구현하는 한 컨테이너의 반복자도 제공해야 합니다. 반복자를 사용하면 컨테이너의 내부 구현 세부 정보를 캡슐화하고, 다양한 컬렉션에 대한 통합된 순회 방법을 제공하며, 클라이언트 액세스 및 컨테이너의 데이터 획득을 단순화한다는 이점이 있습니다. 이를 바탕으로 Iterator를 사용하여 컬렉션 순회를 완료할 수 있습니다. 또한 for loopforeach 구문을 사용하여 컬렉션을 순회할 수도 있습니다. 컬렉션 클래스. ListIterator는 List 컨테이너 계열에 고유한 양방향 반복자입니다. 이 글의 주요 내용은 다음과 같습니다:

  • 반복자 패턴

  • 반복자 반복자 및 반복 가능 인터페이스

  • 루프 순회: foreach, Iterator 및 for

  • ListIterator에 대한 간략한 설명(컨테이너 List에 대한 자세한 설명)


2. 반복자 패턴

 반복자 패턴은 컬렉션과 공생합니다. 일반적으로 말하자면, 컨테이너를 구현하는 한 Java의 Collection(List, Set 등)과 마찬가지로 컨테이너의 반복자도 제공해야 합니다. 자신만의 반복자가 있습니다. 물론 새 컨테이너를 구현하려면 반복자 패턴을 도입하고 컨테이너에 대한 반복자를 구현해야 합니다. 반복자를 사용하면 다음과 같은 이점이 있습니다. 은 컨테이너의 내부 구현 세부 정보를 캡슐화하여 다양한 컬렉션에 대한 통합 순회 방법을 제공하여 클라이언트 액세스를 단순화하고 컨테이너에서 데이터를 얻을 수 있습니다.

하지만 컨테이너와 반복자는 워낙 밀접하게 연관되어 있기 때문에 대부분의 언어에서는 컨테이너 구현 시 해당 반복자를 제공하기도 하며, 대부분의 경우 이러한 언어에서 제공하는 컨테이너 및 반복자는 ​​우리의 필요를 충족시킬 수 있습니다. 따라서 실제로는 반복자 패턴을 직접 구현해야 하는 경우가 비교적 드뭅니다. 언어에서 기존 컨테이너와 반복자만 사용해야 하는 경우가 많습니다.


1. 정의 및 구조

  • 정의

    Iterator(Iterator) 모드, 커서( Cursor 라고도 함) ) 모드. GOF에서 제공하는 정의는 다음과 같습니다. 는 컨테이너 개체의 내부 세부 정보를 노출하지 않고 컨테이너(컨테이너) 개체 의 각 요소에 액세스하는 방법을 제공합니다.

    정의에 따르면 반복자 패턴은 컨테이너용으로 탄생했습니다. 우리는 컨테이너 객체에 대한 액세스에는 순회 알고리즘이 필요하다는 것을 알고 있습니다. 순회 메소드를 컨테이너 객체에 연결하기만 하면 됩니다. 아니면 순회 알고리즘을 전혀 제공하지 않고 컨테이너를 사용하는 사람들이 직접 구현하도록 할 수도 있습니다. 두 경우 모두 문제가 해결되는 것 같습니다. 그러나 전자의 경우 컨테이너는 자체 "컨테이너" 내의 요소 유지 관리(추가, 삭제, 수정, 확인 등)를 담당할 뿐만 아니라 탐색을 위한 인터페이스도 제공합니다. 그리고 마지막으로 중요한 것은 순회 상태 저장 문제로 인해 동일한 컨테이너 개체에 대해 동시에 여러 순회를 수행할 수 없으며 reset 작업을 추가해야 합니다. 두 번째 방법은 수고를 덜 수 있지만 컨테이너의 내부 세부정보가 노출됩니다.


  • 반복자 모드 문자 구성

     

    반복자 역할(Iterator): 반복자 역할 요소에 액세스하고 탐색하기 위한 인터페이스를 정의하는 역할을 담당합니다.

     

    Concrete Iterator 역할(Concrete Iterator): 구체적인 반복자 역할 반복자 인터페이스를 구현하고 순회를 기록합니다. 현재 ; 인터페이스

    ;

    위치

     콘크리트 컨테이너: 콘크리트 컨테이너 역할 구체적인 반복자 역할을 생성하기 위한 인터페이스를 구현합니다 - this 특정 반복자 역할 컨테이너의 구조 와 관련이 있습니다.


  • 구조도
       Java 반복자의 자세한 코드 예

    구조를 보면 알 수 있듯이 >반복자 패턴은 클라이언트와 컨테이너 사이에 반복자 역할을 추가합니다. 반복자 역할을 추가하면 컨테이너 내부 세부 정보의 노출을 효과적으로 방지할 수 있으며 단일 책임 원칙을 준수하는 디자인도 만들 수 있습니다.
     
     반복자 패턴에서는 특정 반복자 역할과 특정 컨테이너 역할이 함께 결합된다는 점에 유의하는 것이 중요합니다 — — 순회 알고리즘은 컨테이너 내부 세부 사항과 밀접한 관련이 있습니다. 특정 반복자 역할과의 결합 딜레마에서 클라이언트 프로그램을 해방하고 특정 반복자 역할의 교체로 인해 클라이언트 프로그램에 발생하는 수정을 피하기 위해 반복자 패턴은 특정 반복자 역할을 추상화하여 클라이언트 프로그램을 보다 일반적으로 만듭니다. 그리고 재사용성, 이것을 다형성 반복이라고 합니다.


  • 적용성

    1. 내부 표현을 노출하지 않고 컨테이너 개체의 콘텐츠에 액세스합니다.

     2. 컨테이너 개체의 다중 순회를 지원합니다.

     3. 다양한 컨테이너 구조를 탐색하기 위한 통합 인터페이스를 제공합니다(즉, 다형성 반복 지원).


2. 예제

Iterator 패턴 자체의 규정이 상대적으로 느슨하기 때문에 구체적인 구현이 다양합니다. 여기에 한 가지 예를 들겠습니다. 예제를 제공하기 전에 먼저 반복자 패턴을 구현하는 방법을 열거해 보겠습니다.

  • 반복자 역할은 순회 인터페이스를 정의하지만 반복을 제어하는 ​​사람은 지정하지 않습니다. Java 컬렉션 프레임워크에서 클라이언트 프로그램은 외부 반복자 라고 하는 순회 프로세스를 제어합니다. 내부 반복자 라고 하는 반복자 자체로 반복을 제어합니다. 외부 반복자는 내부 반복자보다 유연하고 강력하며 Java 언어 환경에서 내부 반복자의 유용성은 매우 약합니다. 패턴 순회 알고리즘을 누가 구현해야 하는지 지정하는 것은 반복자 역할에서는 당연한 일인 것 같습니다.

  • 하나의 컨테이너에 서로 다른 순회 알고리즘을 사용하는 것이 편리하고, 하나의 순회 알고리즘을 서로 다른 컨테이너에 적용하는 것도 편리하기 때문입니다. 그러나 이는 컨테이너의 캡슐화를 파괴합니다. 컨테이너 역할은 자체 비공개
  • 속성

    을 노출해야 합니다. 이는 Java에서 자체 비공개 속성을 다른 클래스에 노출하는 것을 의미합니다.   이를 구현하기 위해 컨테이너 역할에 추가합니다. 이러한 방식으로 현재 위치를 탐색하는 함수만 저장하도록 반복자 역할이 승격됩니다. 그러나 순회 알고리즘은 특정 컨테이너와 밀접하게 연결되어 있습니다. Java Collection 프레임워크에서 제공되는 특정 반복자 역할은 컨테이너 역할에 정의된 내부 클래스

    이므로 컨테이너의 캡슐화를 보호합니다. 그러나 동시에 컨테이너는 순회 알고리즘 인터페이스도 제공하므로 자체 반복자를 확장할 수 있습니다.

    Java 컬렉션의 반복자 구현을 살펴보겠습니다.

    //迭代器角色,仅仅定义了遍历接口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();   
       //抛出异常,迭代终止 
       }
    }

  • 반복자 패턴

    사용 클라이언트 프로그램은 먼저 특정 컨테이너 역할을 획득한 다음 특정 컨테이너 역할을 통해 특정 반복자 역할을 획득해야 합니다. 이런 식으로 특정 반복자 역할을 사용하여 컨테이너를 순회할 수 있습니다...
  • 3. 적용 가능한 상황

    반복자의 적용을 볼 수 있습니다. 패턴을 컨테이너에 적용하면 다음과 같은 이점이 있습니다.
 1)
다양한 방식으로 컨테이너 역할 탐색을 지원합니다. 구현에 따라 효과가 달라집니다(예: List의 iterator 및 listIterator).

 2)

컨테이너 인터페이스를 단순화했습니다.

그러나 Java 컬렉션의 확장성을 향상하기 위해 컨테이너는 여전히 순회 인터페이스를 제공합니다.

  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 个可能的指针位置,如下面的插入符举例说明:

          Java 반복자의 자세한 코드 예

   注意,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数据结构的操作。

위 내용은 Java 반복자의 자세한 코드 예의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.