データベースクエリの結果を繰り返し、各行で何かをしてから、次の行に進むコードがよくあります。代表的な例は以下の通りです。
Cursor cursor = db.rawQuery(...);
cursor.moveToFirst();
while (cursor.isAfterLast() == false)
{
...
cursor.moveToNext();
}
Cursor cursor = db.rawQuery(...);
for (boolean hasItem = cursor.moveToFirst();
hasItem;
hasItem = cursor.moveToNext()) {
...
}
Cursor cursor = db.rawQuery(...);
if (cursor.moveToFirst()) {
do {
...
} while (cursor.moveToNext());
}
これらはすべて私にはあまりにも長すぎるように見え、それぞれがCursor
メソッドを複数回呼び出します。きちんとした方法があるはずですか?
最も簡単な方法はこれです:
while (cursor.moveToNext()) {
...
}
カーソルは前最初の結果行から始まるので、最初の繰り返しで最初の結果存在する場合に移動します。カーソルが空の場合、または最後の行がすでに処理されている場合は、ループは適切に終了します。
もちろん、使い終わったらカーソルを閉じることを忘れないでください。できればfinally
句を使用してください。
Cursor cursor = db.rawQuery(...);
try {
while (cursor.moveToNext()) {
...
}
} finally {
cursor.close();
}
API 19+をターゲットにしている場合は、try-with-resourcesを使用できます。
try (Cursor cursor = db.rawQuery(...)) {
while (cursor.moveToNext()) {
...
}
}
私がカーソルを通過するために私が見つけた最も見栄えの良い方法は次のとおりです。
Cursor cursor;
... //fill the cursor here
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
// do what you need with the cursor here
}
後でカーソルを閉じることを忘れないでください
EDIT:与えられた解決策は、自分が責任を持たないカーソルを繰り返す必要がある場合に最適です。たとえば、メソッドの引数としてカーソルを使用していて、カーソルの現在位置を気にせずにカーソルをスキャンして特定の値を取得する必要がある場合がその例です。
カーソルが開始位置にない場合にも機能する3番目の選択肢を指摘しておきます。
if (cursor.moveToFirst()) {
do {
// do what you need with the cursor here
} while (cursor.moveToNext());
}
Foreachループを使うのはどうでしょうか。
Cursor cursor;
for (Cursor c : CursorUtils.iterate(cursor)) {
//c.doSth()
}
しかしながら私のCursorUtilsのバージョンはそれほど醜くないはずですが、それは自動的にカーソルを閉じます:
public class CursorUtils {
public static Iterable<Cursor> iterate(Cursor cursor) {
return new IterableWithObject<Cursor>(cursor) {
@Override
public Iterator<Cursor> iterator() {
return new IteratorWithObject<Cursor>(t) {
@Override
public boolean hasNext() {
t.moveToNext();
if (t.isAfterLast()) {
t.close();
return false;
}
return true;
}
@Override
public Cursor next() {
return t;
}
@Override
public void remove() {
throw new UnsupportedOperationException("CursorUtils : remove : ");
}
@Override
protected void onCreate() {
t.moveToPosition(-1);
}
};
}
};
}
private static abstract class IteratorWithObject<T> implements Iterator<T> {
protected T t;
public IteratorWithObject(T t) {
this.t = t;
this.onCreate();
}
protected abstract void onCreate();
}
private static abstract class IterableWithObject<T> implements Iterable<T> {
protected T t;
public IterableWithObject(T t) {
this.t = t;
}
}
}
以下がより良い方法かもしれません:
if (cursor.moveToFirst()) {
while (!cursor.isAfterLast()) {
//your code to implement
cursor.moveToNext();
}
}
cursor.close();
上記のコードは、それが繰り返し全体を通過し、最初と最後の繰り返しから逃げることがないように保証します。
import Java.util.Iterator;
import Android.database.Cursor;
public class IterableCursor implements Iterable<Cursor>, Iterator<Cursor> {
Cursor cursor;
int toVisit;
public IterableCursor(Cursor cursor) {
this.cursor = cursor;
toVisit = cursor.getCount();
}
public Iterator<Cursor> iterator() {
cursor.moveToPosition(-1);
return this;
}
public boolean hasNext() {
return toVisit>0;
}
public Cursor next() {
// if (!hasNext()) {
// throw new NoSuchElementException();
// }
cursor.moveToNext();
toVisit--;
return cursor;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
コード例:
static void listAllPhones(Context context) {
Cursor phones = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
for (Cursor phone : new IterableCursor(phones)) {
String name = phone.getString(phone.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String phoneNumber = phone.getString(phone.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Log.d("name=" + name + " phoneNumber=" + phoneNumber);
}
phones.close();
}
Do/Whileソリューションはより洗練されていますが、moveToPosition(-1)を使用せずに上記のWhileソリューションのみを使用すると、(少なくともContactクエリで)最初の要素が欠落します。
私は提案します:
if (cursor.getCount() > 0) {
cursor.moveToPosition(-1);
while (cursor.moveToNext()) {
<do stuff>
}
}
if (cursor.getCount() == 0)
return;
cursor.moveToFirst();
while (!cursor.isAfterLast())
{
// do something
cursor.moveToNext();
}
cursor.close();
最初は、moveToNext()
を使用して最初の行にカーソルが表示されません。レコードが存在しない場合はカーソルを反復処理でき、return false
は、return true
でない限り、
while (cursor.moveToNext()) {
...
}
cursorは、あらゆるデータベースの2-dimensional
テーブルを表すインタフェースです。
SELECT
文を使用してデータを取得しようとすると、データベースは最初にCURSORオブジェクトを作成し、その参照を返します。
この返された参照のポインタは、カーソルの最初の位置の前と同じように呼び出される番目の位置を指しているため、カーソルからデータを取得するときは、最初に移動する必要があります。最初のレコードなので、moveToFirstを使用する必要があります
Cursor、に対してmoveToFirst()
メソッドを呼び出すと、カーソルポインタが1番目の位置に移動します。これで、1番目のレコードにあるデータにアクセスできます
見ての最善の方法:
カーソルカーソル
for (cursor.moveToFirst();
!cursor.isAfterLast();
cursor.moveToNext()) {
.........
}