JavaでのIterableとIteratorの使い方の詳細

5684 ワード

Javaでは、リストセットを次のように巡回できます.

List list = new ArrayList<>();
list.add(5);
list.add(23);
list.add(42);
for (int i = 0; i < list.size(); i++) {
  System.out.print(list.get(i) + ",");
}
 
Iterator it = list.iterator();
while (it.hasNext()) {
  System.out.print(it.next() + ",");
}
 
for (Integer i : list) {
  System.out.print(i + ",");
}

1つ目は普通のforサイクルで、2つ目は反復器遍歴で、3つ目はfor eachサイクルです.
Javaのiteratorオブジェクトとiterableオブジェクトの2つの方法について説明します.次に、この2つのオブジェクトの違いと、カスタムクラスでfor eachループを実現する方法を見てみましょう.
IteratorとIterable
iteratorはJavaにおける反復器オブジェクトであり,Listのような集合を反復的に遍歴できる下位依存である.一方、iterableインタフェースでは、iteratorを返す方法が定義されており、iterableインタフェースのクラスがfor eachループをサポートすることができるように、iterableインタフェースをカプセル化することに相当します.
iterator内部詳細
jdkのIteratorインタフェースの主な方法は以下の通りです.

public interface Iterator {
  boolean hasNext();
  E next();
}

iteratorは以上の2つの方法によって集合への反復アクセスの方法を定義し,具体的な実装方式は異なる実装クラスに依存し,具体的な集合クラスはIteratorインタフェースの方法を実装して反復を実現する.
リストにはIteratorインタフェースが実装されていないが,実装されたIterableインタフェースが実装されていることがわかる.さらにIterableインタフェースのソースコードを観察すると、Iteratorオブジェクトが返されただけであることがわかります.

public interface Iterable {
 Iterator iterator();
}

したがって、リストを反復するには、(iterator()メソッドを呼び出すことによって)次の方法を用いることができる.

Iterator it = list.iterator();
while (it.hasNext()) {
  System.out.print(it.next() + ",");
}

同時にIterableインタフェースを実現し、for eachループを使用することもできます.
for each原理
実はfor eachサイクルの内部もIterator反復器に依存していますが、Javaが提供する文法糖にすぎず、JavaコンパイラはそれをIterator反復器に変換して遍歴します.次のfor eachサイクルを逆コンパイルします.

for (Integer i : list) {
   System.out.println(i);
 }

逆コンパイル後:

Integer i;
for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println(i)){
    i = (Integer)iterator.next();    
  }

Javaのfor each増強サイクルはiterator反復器方式で実現されていることがわかります.
IterableとIteratorの関係を深く検討する
なぜhasNext(),next()メソッドをIterableインタフェースに直接置かず,他のクラスが直接実装すればよいのかという問題がある.
なぜなら、一部の集合クラスは、LinkedListのListItrとDescendingIteratorの2つの内部クラスのような複数のIterator内部クラスを実装することができ、それぞれ双方向遍歴と逆シーケンス遍歴を実現する遍歴方式ではない可能性があるからである.異なるIteratorを返すことで、異なる遍歴方式を実現することで、より柔軟になります.2つのインタフェースを統合すると、異なるIterator実装クラスに戻ることはできません.ListItr関連ソースコードは以下の通りである.

public ListIterator listIterator(int index) {
  checkPositionIndex(index);
  return new ListItr(index);
}
 
private class ListItr implements ListIterator {
  ...
  ListItr(int index) {
    // assert isPositionIndex(index);
    next = (index == size) ? null : node(index);
    nextIndex = index;
  }
 
  public boolean hasNext() {
    return nextIndex < size;
  }
  ...

上記のようにlist.listIterator()メソッドを呼び出すことでiterator反復器を返すことができます(list.iterator()はデフォルトの実装にすぎません)
DescendingIteratorのソースコードは次のとおりです.

public Iterator descendingIterator() {
  return new DescendingIterator();
}
private class DescendingIterator implements Iterator   {
  private final ListItr itr = new ListItr(size());
  public boolean hasNext() {
    return itr.hasPrevious();
  }
  public E next() {
    return itr.previous();
  }
  public void remove() {
    itr.remove();
  }
}

この反復器はlist.descendingIterator()でも使用できます.
自分の反復器を実現する
カスタムクラスArrayMapがあり、次のようにfor eachを巡回します.

ArrayMap am = new ArrayMap<>();
am.put("hello", 5);
am.put("syrups", 10);
 
for (String s: am) {
  System.out.println(s);
}

hashNextとnext抽象法は実装されていないため,それを遍歴することはできない.
カスタム反復クラス
まず,反復器クラス実装hashNextとnextメソッドをカスタマイズし,ArrayMapの内部クラスとして用い,関連コードは以下の通りである.

public class KeyIterator implements Iterator {
   private int ptr;
 
   public KeyIterator() {
     ptr = 0;
   }
 
   @Override
   public boolean hasNext() {
     return (ptr != size);
   }
 
   @Override
   public K next() {
     K returnItem = keys[ptr];
     ptr += 1;
     return returnItem;
   }
 }

nextで指定した遍歴規則はArrayMapのkey値に基づいて遍歴していることがわかります.上記の反復器クラスがあれば、iterator方式を使用して外部で遍歴することができます.遍歴コードは以下の通りです.

ArrayMap am = new ArrayMap<>();
am.put("hello", 5);
am.put("syrups", 10);
ArrayMap.KeyIterator ami = am.new KeyIterator();
while (ami.hasNext()) {
  System.out.println(ami.next());
}

上記のように、外部クラスが内部クラスオブジェクトを作成する方法に注意して、KeyIteratorオブジェクトを作成することで反復アクセスを行います.
for eachサイクルをサポート
まだiterableインタフェースを実装していないため、for eachループアクセスはサポートされていません.まずArrayMapでIterableインタフェースを実装します.

public class ArrayMap implements Iterable {
 
  private K[] keys;
  private V[] values;
  int size;
 
  public ArrayMap() {
    keys = (K[]) new Object[100];
    values = (V[]) new Object[100];
    size = 0;
  }
 ....
}

次にiterator()メソッドを書き換え、独自の反復オブジェクト(iterator)を返します.

@Override
public Iterator iterator() {
  return new KeyIterator();
}

カスタムのKeyIteratorクラスでは、iterator()メソッドで返されるタイプが一致しない場合は、Iteratorインタフェースを実装する必要があります.