S-JIS[2019-11-28] 変更履歴

Java Iterator

JavaのIteratorについて。


概要

java.util.Iteratorは、(デザインパターンのひとつである)イテレーターパターンを実現するインターフェース。(JDK1.2から有る)
コレクションListSet等の、値を複数持つクラス)等の要素をひとつずつ順次処理するのに使う。

import java.util.Iterator;
	Iterator<String> i = ;
	while (i.hasNext()) {
		String s = i.next();
		System.out.println(s);
	}

コレクションの要素を順次処理するという意味では、JDK1.5(Java5)で導入されたfor-each構文を使う方が分かりやすい。

	Iterable<String> iterable = 〜; // コレクション
	for (String s : iterable) {
		System.out.println(s);
	}

また、JDK1.8(Java8)でコレクション(ListやSetの親であるIterable)にforEachメソッドが実装されたので、それとラムダ式を使う方法もある。

	Iterable<String> iterable = 〜; // コレクション
	iterable.forEach(s -> System.out.println(s));	

IteratorからIterableへの変換方法


Iteratorの例

Iteratorインターフェースは、hasNextメソッドとnextメソッドを持っている。
hasNextメソッドで要素がまだ有るかどうか判定し、有る場合はnextメソッドでその値を取得する。
要素が存在する間、hasNextとnext呼び出しを繰り返す。

whileを使う例 forを使う例
Iterator<String> i = ;
while (i.hasNext()) {
  String s = i.next();
  System.out.println(s);
}
for (Iterator<String> i = ; i.hasNext();) {
  String s = i.next();
  System.out.println(s);
}

個人的には、Iteratorの変数のスコープを限定する事が出来るので、for文を使う方が好み。


要素が無くなってからnextメソッドを呼び出すとNoSuchElementExceptionが発生する。

最初からIteratorに1件も要素が無い場合(空のIteratorの場合)、hasNextメソッドは常にfalseを返す。

一度Iteratorの要素が無くなると、そのIteratorで再度ループさせることは出来ない。


「最初に必ずhasNextメソッドを呼ばなければならない」といったルールは無いので、データが必ず有ると分かっている場合は、いきなりnextメソッドを呼び出す事も可能。

	Set<String> set = Set.of("a", "b", "c");
	assert !set.isEmpty();
	Iterator<String> i = set.iterator();
	String s = i.next();

forEachRemainingの例

JDK1.8(Java8)で、IteratorにforEachRemainingメソッドが追加された。

	Iterator<String> iterator = ;

	iterator.forEachRemaining(s -> {
		System.out.println(s);
	});

要するにコレクションStreamのforEachメソッドと同様にラムダ式(あるいはメソッド参照)を渡して全要素の処理が出来る。

メソッド名にわざわざ「Remaining」という単語が付いているのは、Iteratorを途中までループさせてからforEachRemainingメソッドを呼び出すと、残りの要素に対して処理を行う(Iteratorの最初からではない)為だと思う。

for-each構文を使用する方法


removeの例

ミュータブル(要素の削除が可能)なコレクションから取得したIteratorの場合、Iteratorでループしながら元のコレクションの要素を削除することが出来る。

	List<String> list = new ArrayList<>(List.of("a", "", "c"));
	System.out.println("before " + list);

	for (Iterator<String> i = list.iterator(); i.hasNext();) {
		String s = i.next();
		if (s.isEmpty()) {
			i.remove();
		}
	}

	System.out.println("after " + list);

↓実行例

before[a, , c]
after [a, c]

i.next()を呼び出すことによってイテレーター内部では次の要素へ進んでいるような気がしてしまうが、
i.remove()を呼び出すと、直前のi.next()で取得していた要素が削除される。


Iteratorの取得方法

ListSet等のコレクション(の親であるIterableインターフェース)はiteratorメソッドを持っているので、それを呼び出せばIteratorを取得できる。

import java.util.Iterator;
import java.util.List;
	List<String> list = 〜;
	Iterator<String> i = list.iterator();

Mapにはiteratorメソッドは無いので、entrySetkeySetvaluesメソッドを呼び出してコレクションを取得し、そこからIteratorを取得する。


空のIteratorが欲しい場合はCollectionsを使用する。(JDK1.7(Java7)以降)

import java.util.Collections;
	Iterator<String> iterator = Collections.emptyIterator();

JDK1.7より前の場合は、emptyListからIteratorを取得しておけばいいと思う。

	Iterator<String> iterator = Collections.<String> emptyList().iterator();

Streamとの変換

Java8のStreamiteratorメソッドを持っているので、StreamからIteratorを取得するのは簡単に出来る。
(Streamのiteratorメソッドは終端処理なので、2回以上Iteratorを取得することは出来ない)


IteratorからStreamを取得するメソッドは無いので、以下のようにして変換する。

import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
	Iterator<String> iterator = ;
	Spliterator<String> spliterator = Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED);
	Stream<String> stream = StreamSupport.stream(spliterator, false);

Iterableへの変換(for-each文での使用方法)

IteratorをIterableに変換すれば、for-each構文で使用することが出来る。

IteratorインターフェースはIterableを継承しているわけではなく、Iterableを取得するメソッドを持っているわけでもない。
しかしIterableはIteratorを返すメソッドだけ実装すればいいので、独自のIterableを作ればよい。

	Iterator<String> iterator = ;

	Iterable<String> iterable = new Iterable<>() {
		@Override
		public Iterator<String> iterator() {
			return iterator;
		}
	};

	// for-each構文
	for (String s : iterable) {
		System.out.println(s);
	}

ところで、Iterableインターフェースには@FunctionalInterfaceアノテーションは付いていないのだが、関数型インターフェースの条件を満たしているので、関数型インターフェースとして使用できる。
そこで、Iterableは以下のようにラムダ式を使って記述することが出来る。

	Iterator<String> iterator = ;

	Iterable<String> iterable = () -> iterator;

	// for-each構文
	for (String s : iterable) {
		System.out.println(s);
	}

ラムダ式を直接for文に書く場合は、Iterableへのキャストが必要となる。
(for文は「:」の右側の式の部分をIterableとして推論してくれたりはしないから(参考:nagiseさんのツイート)、ラムダ式がどの関数型インターフェースなのかを明示する必要がある)

	Iterator<String> iterator = ;

	// for-each構文
	for (String s : (Iterable<String>) () -> iterator) {
		System.out.println(s);
	}

Iteratorを別クラスからメソッドで取得できる場合(例えばStreamからはiteratorメソッドで取得できる)は、メソッド参照を使って記述することも出来る。
(ListやSetといったコレクションは直接Iterableを継承しているので、変な小細工をしなくてもfor-each構文を使うことが出来る)

	Stream<String> stream = 〜;

//	Iterable<String> iterable = () -> stream.iterator();
	Iterable<String> iterable = stream::iterator;

	// for-each構文
	for (String s : iterable) {
		System.out.println(s);
	}
	Stream<String> stream = 〜;

	// for-each構文
	for (String s : (Iterable<String>) stream::iterator) {
		System.out.println(s);
	}

Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま