JavaとIteratorその① 外部イテレータ編


Iteratorというものについて、ちょいちょい聞いてたことはあって
なんとなく繰り返し処理に関する単語なんだろうなくらいの認識はあったのですが、
実際具体的に何を示しているのかよくわかっていなかったので、調べてみました。

まず…

ひとくちにIteratorなる単語で調べても、複数の意味合いで使われているようで最初混乱しました。
Javaの文脈に限るなら、主に以下2つの意味合いで使われているようです。

 1. GoF本によって定義された、コンテナオブジェクトを列挙する手段を提供するデザインパターン
 2. java.util.Iteratorにて定義されるIteratorインターフェイス

Iteratorっていうデザインパターンがまずあって(1)、
これをJava言語の仕様に組み込んだのがIteratorインターフェイス
(2)。

このあたり一度整理できると、文脈からどちらの意味合いで使われているか理解できるようになって
だいぶ情報収集が楽になりました。

Iteratorパターン とは?

wikiによるとこんな感じ

コンテナオブジェクトの要素を列挙する手段を独立させることによって、
コンテナの内部仕様に依存しない反復子を提供することを目的とする。

技術用語難しい…、コンテナというのが何かよくわからなかったのでさらにwikiりました

コンテナとはオブジェクトの集まりを表現するデータ構造、抽象データ型またはクラスの総称である。

コンテナはコレクションと読み替えてよさそう…?
よさそうとして、あらためてwikiのIteratorパターンの内容を大雑把に翻訳すると

Listとか配列とかMAPとか、オブジェクトの型自体の定義と、繰り返し処理の方法を別個に定義することで
コレクションの型に関係なく、おんなじ方法で繰り返し処理をできるようにするよ!

という感じですかね?

拡張for文とかStreamAPIとか使うとき、私たち
list<String> hogelist = new ArrayList<>();
だろうが
String[] hogelist = new String[2];
だろうが、かまわず

for (String hoge : hogeList) { System.out.println(hoge); }

てかんじに、型にかまわず同じようにかけていましたけれど
これって実はIteratorパターンにのっとった実装が、ぱっとみ意識しないでいいとこに定義されてるからなんですよ!
っていう…!

Iteratorインターフェイス

ということで、Iteratorインターフェイスがでてきます。
ぱっとみ、意識しないでいいとこに何が定義されてるんですか!?っていうとそれが
コレクションオブジェクトIteratorインターフェイスを実装しているのです。

コメントにてご指摘いただいたのですが、正確にいうと
コレクションオブジェクトが実装しているIterableインターフェースによって
Iteratorインターフェイスを呼び出すことができる!らしいです。

…一人で勉強していると、勘違いに気づかずがんがん進んでいくので
指摘もらえるの本当にありがたいです、ありがとうございます…!

Iteratorインターフェイスのメソッドはこんな感じ。

戻り型  メソッド  説明 
boolean hasNext( ) 繰り返し処理において、次の要素がある場合にtrueを返します。
Object next( ) 繰り返し処理において、次の要素を返します。
void remove( ) 繰り返し処理において呼び出された、最後の要素を削除します。

Iterableインターフェースはこんな感じ

戻り型  メソッド  説明 
void forEach(Consumer<? super T> action) Iterableの各要素に対して指定されたアクションを、すべての要素が処理されるか、アクションが例外をスローするまで実行します。
Iterator iterator() 型Tの要素のイテレータを返します。
Spliterator spliterator() このIterableによって記述される要素に対するSpliteratorを作成します。

…これ、公式サイトの説明がカードゲームの説明みたいな言い回しでちょっと面白いです↓

このインタフェースを実装すると、オブジェクトを「for-eachループ」文の対象にすることができます。

for-eachループ = 拡張for文。

整理すると、
ListやMAPなんかのコレクションは、コレクションの性質を定義している
Iteratorインターフェイスは繰り返し処理の仕方を定義している
コレクションにIterableインターフェースを実装すると、Iteratorインターフェイスを呼び出せる

ListもMAPも、コレクションがIterableインターフェイスを実装している限り
Iteratorインターフェイスを取得できる
ゆえにコレクションの型に関わらず、Iterableインターフェイスを実装しているなら繰り返し処理を実装できる、です。

そして、これもコメントで教えていただいたのですが
配列はIterableインターフェイスを実装していないんですね…!
してないけれど、拡張forくんが配列だけ特別扱いして、普通のfor文に解釈してくれるみたいです。

Java繰り返し処理とIteratorメソッド

実際に拡張forなり、StreamAPIなりを使ってるときにhasNext()だのremove( ) だのを
見た覚えってないんですよね、まるでなじみがない。

Javaさんがよしなにこっそりあれこれやってくださってるからなんですけれど
鶴の恩返しよろしく、しょうじの向こう側ってのぞいてみたい…そして納得したい…
逆コンパイルして、拡張FOR文の内部処理を記載している方がいらっしゃったので

以下にこちら掲載のコードを引用させていただきます。

リストの場合

import java.util.ArrayList;
import java.util.List;

public class IteratorSample3 {
    public static void main(String[] args) {
        List list = new ArrayList();

        for (int i = 0; i < 10; i++) {
            list.add(new Integer(i));
        }

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

import java.io.PrintStream;
import java.util.*;

public class IteratorSample3
{

    public IteratorSample3()
    {
    }

    public static void main(String args[])
    {
        ArrayList arraylist = new ArrayList();
        for(int i = 0; i < 10; i++)
            arraylist.add(new Integer(i));

        Object obj;
        for(Iterator iterator = arraylist.iterator(); 
                                 iterator.hasNext();
                                 System.out.println((Integer)obj))
            obj = iterator.next();

    }
}

配列の場合

public class IteratorSample5 {
    public static void main(String[] args) {
       int[] numbers = new int[10];

        for (int i = 0; i < 10; i++) {
            numbers[i] = i;
        }

        for (int i : numbers) {
            System.out.println(i);
        }
    }
}


import java.io.PrintStream;

public class IteratorSample5
{

    public IteratorSample5()
    {
    }

    public static void main(String args[])
    {
        int ai[] = new int[10];
        for(int i = 0; i < 10; i++)
            ai[i] = i;

        int ai1[] = ai;
        int j = ai1.length;
        for(int k = 0; k < j; k++)
        {
            int l = ai1[k];
            System.out.println(l);
        }

    }
}

配列の場合はごくごく普通のインデックスを用いたアクセスになっていました。相手を見て変換の方法を変えているようです。

コレクションオブジェクトだったら、Iteratorを使ったイテレーション処理
配列ならシンタックスシュガーとして普通のforでまわすように、本当にコンパイラさんが判断している…!
こんなん自力だったら調べられなかったです。探求力と技術力がある人が知識を公開してくださるの、本当にありがたい…

Iteratorパターンは2つある

ここまで、Java…というか、拡張FOR文を使ってIteratorパターンとかIteratorメソッドについて
書いていたんですが、実はこれは「外部イテレータ」というイテレータのうちの半分の説明でしかなかったのです…!

最近…ではもう全くないんですけれどJava8さんで使うようになったStreamAPI!
こいつはイテレータの残り半分「内部イテレータ」というなにがしかが実装できるようになっているらしいですよ…!

…という感じで、力尽きたのでいったんここまでで公開します。
外部イテレータといいつつ現状ほぼ拡張forの説明になっているので、拡張forいがいも後程追記しますね…

日本語や文章構造の整理、嘘とかついていたら内容の修正をしていきつつ
②として、外部イテレータを勉強したまとめを近日中に書いてみようとおもいます。
…記事の形にするって大変なのだな。

8/30 …コメントで指摘いただいた内容と日本語一部修正

次回整理する予定のキーワード

並列処理
クロージャ
内部イテレータ

あと、コメントいただいてシンタックスシュガーという名称をはじめて知ったので
シンタックスシュガーさんについてと、配列とコレクションの違いとかもちゃんと調べてまとめたい…

参考サイト

再考: GoF デザインパターン
Javaのイテレータ(拡張for構文)
インタフェースIterator
Java2 SE 5.0 虎の巻 拡張for文
Java SE 8、Stream APIの基本を理解する