Javaのfor-each文でremoveする

Javaの for-each 文 (拡張for文) 内で remove をする方法はありません。
Sunの解説でもこの点は明記されています。

for-each ループは好きなときに使えます。使用すればコードがわかりやすくなりますが、どこででも使用できるわけではありません。たとえば、expurgate メソッドについて考えてみましょう。現在の要素を削除するには、反復子にアクセスする必要があります。for-each ループでは反復子が隠されるため、remove メソッドを呼び出すことができません。そのため、for-each ループをフィルタリングに使用することはできません。

http://java.sun.com/j2se/1.5.0/ja/docs/ja/guide/language/foreach.html


けど、代わりの方法があります。
あとでまとめて removeAll すればOK!

public boolean removeIfStartWith(Collection<String> c, String prefix){
	List<String> stub = new ArrayList<String>();
	for(String str : c){
		if(str.startsWith(prefix)){
			stub.add(str);
		}
	}
	
	return c.removeAll(stub);
}


一時オブジェクトが必要になるのが弱点ですが、そのかわりにイテレータよりも分かりやすくなる気がします。
若干ケースバイケースですが*1、もしイテレーターでぐちゃぐちゃしたときにお試しください!



2010/11/17 追記:
以下のすべてに該当する場合、この方法を使用できません。

  • 引数 Collection の実装が、重複する要素を許可するもの (Listなど)
  • 重複する要素があった場合、そのいずれかしか削除しない

たとえば、「リストの中から、重複する要素のうち一つを残して削除する*2」などの処理が挙げられます。
これは、removeAll メソッドが仕様上、引数のコレクションに含まれる要素を順序に関係なくすべて削除するためです。

逆に、上記のプログラムのように重複する要素がすべて削除の対象になる場合、この方法を問題なく使用できます。


参考:上記の例をイテレータで処理するメソッド

public boolean removeIfStartWith(Collection<String> c, String prefix){
	boolean modified = false;
	for(Iterator<String> ite = c.iterator(); ite.hasNext(); ){
		String str = ite.next();
		if(str.startsWith(prefix)){
			ite.remove();
			modified = true;
		}
	}
	
	return modified;
}

*1:たとえば、上の例でも削除したかどうかを返却しなければ、あんまり変わらなくなります。

*2:具体例:{"add", "add", "addAll", "clear", "contains"}から最初の"add"は残し、次の"add"は削除する