Java 5で導入された拡張for文(for-each文とも呼ばれる)は、主に、オブジェクトのコレクションに対する繰り返し処理を行うために利用される。通常のfor文とは異なり、ループ変数への代入はオブジェクトに対する繰り返しの順序に影響を与えない。したがって、ループ変数への代入は、プログラマの意図に反した意味を持つかもしれない。このことからも、拡張forループのループ変数への代入は避けるべきである。
Java言語仕様§14.14.2 「拡張for文」には以下のように規定されている。
以下の形の拡張 for 文について考える。
for (ObjType obj : someIterableItem) { // ... }これは、以下に示す通常の for ループと等価である。
for (Iterator myIterator = someIterableItem.iterator(); myIterator.hasNext();) { ObjType obj = myIterator.next(); // ... }
つまり、ループ変数への代入と、ループ本体をスコープとしループのイテレータが参照するオブジェクトを初期値とする変数を変更することは、等価である。このようにループ変数に変更を加えることは、必ずしも間違いであるとは言えないが、ループの動作が分かりづらくなるだろうし、そもそもプログラマが拡張 for 文の動作を誤解していることを意味するかもしれない。
拡張for文のループ変数は必ずfinal宣言すること。final宣言することで、コンパイラはループ変数への代入を検出し、変数への代入を拒む。
違反コード
以下の違反コード例では、拡張for文を用いてオブジェクトのコレクションに対する処理を行っている。同時に、コレクションのある要素に対する処理をスキップすることを意図している。
Collection<ProcessObj> processThese = // ... for (ProcessObj processMe: processThese) { if (someCondition) { // 処理をスキップする要素を発見 someCondition = false; processMe = processMe.getNext(); // 次の要素に処理をスキップ } processMe.doTheProcessing(); // そのオブジェクトを処理する }
代入は問題なく実行され、processMeの値が更新されるため、次の要素に処理をスキップする操作は成功するように見える。しかし、標準のfor文とは異なり、代入を行ってもループの繰返し順序は変わらない。結果として、スキップされた要素に続くオブジェクトが2度処理されてしまう。
processMeがfinal宣言されていれば、代入でコンパイルエラーになることに注意。
適合コード
以下の適合コードはコレクションの各オブジェクトを正しく1度だけ処理する。
Collection<ProcessObj> processThese = // ... for (final ProcessObj processMe: processThese) { if (someCondition) { // 処理をスキップする要素を発見 someCondition = false; continue; // 次のイテレーションにcontinueすることで処理をスキップ } processMe.doTheProcessing(); // そのオブジェクトを処理する }
リスク評価
拡張for文(for-each文)のループ変数への代入は、繰り返し処理の順序に影響を与えない。これは、プログラマを混乱させたり、データを壊れやすく不整合な状態に置きかねない。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
DCL02-J | 低 | 低 | 低 | P3 | L3 |
自動検出
このルールは静的解析により容易にチェックできる。
参考文献
[JLS 2005] | §14.14.2, The Enhanced for Statement |
翻訳元
これは以下のページを翻訳したものです。
DCL02-J. Declare all enhanced for statement loop variables final (revision 65)