2011年12月7日水曜日

JavaのArrayListの削除時の注意点


AndroidやJavaではよくArrayListを頻繁に使用する。
特にAndroidではListViewにアタッチする時によく利用されるようだ。
そこで私がしでかした失敗談を一つ。

ArrayListの要素を削除する場合removeメソッドを使用する。
このremoveメソッドは要素を削除した場合
indexも変更になる事に気をつけなければならない。
例えば

ArrayList<String> arrayList = new ArrayList<String>[];
arrayList.add("AAA");
arrayList.add("BBB");
arrayList.add("CCC");

とした際にまとめて削除したい場合
for( int i = 0 ; i < arrayList.size() ; i++ ){
    arrayList.remove(i);
}
とかするとエラーになってしまう。
なぜならsizeで取得した時点の要素数は3だが
removeを実行した瞬間にsizeが変わる。
index0番目(i=0)に相当するAAAを削除したらsizeはその時点で2になるのだ。
そしてindex1番(i=1)はBBBを指してはおらずCCCを指す事になる。
この時点でBBBは残りCCCは削除されるが
次のindex2番(i=2)はremoveをかけた瞬間にエラーとなる。
そう。要素が既に無いのだ。
3個消したい場合これではだめ。
全部消したいのなら
arrayList.clear()とするか
for( int i = 0 ; i < arrayList.size() ; i++ ){
    arrayList.remove(0);
}
などとすればよいだろう。
だが例えば「1番目と3番目を削除したい」としたらどうだろう。
理論的にはindex0を削除したあとindex1を削除すればいいのだが・・・。
「減っていくなら最後の方から削除すればいいのに」という声が聞こえた。

for( int i = arrayList.size() - 1 ; i >= 0 ; i-- ){
    if ( i == 0 ){
        arrayList.remove(i);
    }
    if ( i == 2 ){
        arrayList.remove(i);
    }
}

こんな感じ?

2011年12月4日日曜日

Androidプログラミングメモ 2011/12/4

simple_list_item_multiple_choiceのListViewを使用していて
lv.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
などとした時にチェックされた物を処理したい場合に
getCheckedItemPositions()を使おうと思ったんだけどどうも違うみたい。
正しくは「チェックしたものがある時にすべてのリストを返す」物みたい。
例えば以下の直感コードではうまく動かない。

SparseBooleanArray positions = lv.getCheckedItemPositions();
for (int i = 0; i < positions.size(); i++) {
    if(positions.valueAt(i)){
        data.remove(positions.keyAt(i));
    }
}

一個だけだとうまく行く時もあるけど二個以上のチェックに失敗したり
データを追加したりした後のデータ削除などすると目もあてられない。
SparseBooleanArrayのsize()はListViewの最大値を返してくれるし
keyAt(i)なんかはよくわからない数値を出してくるし。

ListViewが何百もあった場合は結構つらそうだけど以下のように地道に
処理した方が無難みたい。

for (int ii = 0; ii < lv.getChildCount(); ii++) {
    CheckedTextView vv = (CheckedTextView)lv.getChildAt(ii);
    if (vv.isChecked()) {
        //チェックしたものの作業
        data.remove(ii);
    }
}

また、これはAndroidだけではない事だけどデバッグなどで
データを見たいからと言って

Log.v(getClass().getSimpleName(), "values: " + data.toString());
data.remove(ii);
Log.v(getClass().getSimpleName(), "values: " + data.toString());

とかするとデータの無いものを見たりしようとするので
馬鹿な真似はしないように。