繰り返し処理時にArrayList中の要素を削除してもConcurrentModificationExceptionにならない場合(Java)
最近、仕事でJavaを使うことが多かったので、知識のまとめにもなると考えてJavaのSilverの試験を受験しようとしている。
曖昧な知識が整理されて勉強自体が知識の補強に役立っていると感じます。
勉強でこの本
を使っているが、その中の第4章「配列の使用と作成」の17と18の問題に、以下のようなプログラムがある。
その17番のプログラム(p.133)
public class Main {
public static void main(String args) {
ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
for(String str: list) {
if("B".equals(str)) {
list.remove(str);
} else {
System.out.println(str);
}
}
}}
18番のプログラム(p.134)
public class Main {
public static void main(String args) {
ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");for(String str: list) {
if("C".equals(str)) {
list.remove(str);
}
}
for(String str: list) {
System.out.println(str);
}}
}
これらのプログラムをコンパイル、実行したときの結果として、正しいものを選べ、というのが設問である。
どちらのプログラムも、要素がString型のArrayList型の変数listを、拡張forループで処理している。条件に合致したときに、listから特定の要素をArrayListのremoveメソッドで削除している。
その場合、通常は繰り返し処理中にArrayListのremoveメソッドで要素を削除すると、ConcurrentModificationExceptionがthrowされる。
ArrayList (Java 2 Platform SE 5.0)
18のプログラムは、実行すると、実際にその例外がなげられる。

しかし、17のプログラムは実行してもConcurrentModificationExceptionがthrowされず、実行できてしまう。

これがなぜかわからなかった。
しかし、検索して調べてみて
この方のブログをみて納得した。
Listの最後から2番目の要素を削除すると、例外を投げることなく不正な状態で反復処理を終えてしまうそうです。
そのような、繰り返し処理中に要素を削除するようなこと自体が、言語の仕様を満たさない使用法なので、公式APIドキュメントにも言及がないらしいです。
繰り返し処理中に変更されるコレクションのことに関しては、以下の記事がわかりやすいと感じました。
MSC06-J. 繰り返し処理中に基となるコレクションを変更しない
ややこしいので、問題集にもこのことを一言書いてあるとありがたかったですが・・。
(問題集自体は非常にいい本で買ってよかったと思っています)
以上
レジスタをたたくって何
以前、組み込みシステム開発(とある製品のファームウェア開発)の現場で作業をすることになり、チームリーダーの方から通常のアプリケーション開発と組み込みシステム開発の違いを一言でいえば・・というような話をオリエンテーションでされたことがあります。
最大の違いはレジスタを扱うかどうかだ、ということでした。
レジスタというと、組み込みなどハード関係のことに詳しくない方はCPU内の一時記憶場所、というようなイメージしかないかもしれません。私もそうでした。
しかし、組み込みの世界でレジスタといえば、
http://itroner.blog41.fc2.com/blog-entry-3.html
『組み込み機器では、いわゆるCPUだけではなく周辺機器(シリアルポートやLANポートとか)を制御する機能や特殊な演算(音声圧縮とか映像デコードと か)をする機能などを1チップ内に集積しています。これらをCPUから使用するために、各機能と入出力をおこなう領域を物理メモリ上に配置しています。こ の領域がレジスタです。』
出典「大なり小なり 組み込みで遊んでみるブログ http://itroner.blog41.fc2.com/」
といようなものです。
単純に、かつ、おおざっぱに云えば、レジスタの値を読んだり、値を書き込んだりするのが組み込みの中心的な処理といっていいかもしれません。
ハードウェアの仕様はきまっていて(仕様書があり)、この機能はレジスタのこのアドレス上の値を操作すると動かすことができる、ということになっています。
レジスタの値を読み書きすることで、その機能を使用したり、ステータス(状態)をしることができます。
例として挙げます(愚にもつかないようなコードですが)。
*((uint32_t *)0x00000002) = 0x00000001;
この処理は、レジスタの0x00000002番地に0x00000001という値を書き込む、という処理です。uint32_tというのは32ビット長の無符号整数宣言です。
とある機能のレジスタ(ここでは0x00000002番地)上に決められた値を書き込むことで(ここでは0x00000001)その機能を使用することができるようになります。