Androidカーソルを反復するための最良の方法は何ですか?


291

データベースクエリの結果を反復処理し、各行で何かを実行してから、次の行に移動するコードをよく目にします。代表的な例を以下に示します。

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メソッドへの複数の呼び出しがあります。きっとすてきな方法があるに違いない?


1
これの目的は何ですか?投稿後1分以内に自分で回答しました...
Barak

10
質問と同時に答えました
Graham Borland

1
ああ、そのリンクを見たことがない。どうやらすでに答えが出ていると思われる質問をするのはばかげているように思えました。
Barak

5
@バラク:彼が投稿してくれたことは素晴らしいことだと思います-今私は何かをするための少しすっきりした方法を知っています。
ジョージ

4
あなたがこれを投稿して、見に来る可能性のある人を助けるためにあなたがこれを投稿したことは私には明らかなようです。これに対するあなたへの小道具、そして役立つヒントへの感謝!
muttley91 2013

回答:


515

最も簡単な方法はこれです:

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()) {
        ...
    }
}

19
したがって、カーソルを事前に任意の位置に置いてこの反復を実行したい場合は、whileループの前にcursor.moveToPosition(-1)を使用しますか?
2013年

43
それを閉じることを忘れないでください!
サイモン

13
SQLiteデータベースクエリがnullを返すことはありません。結果が見つからない場合は、空のカーソルを返します。ただし、ContentProviderクエリはnullを返す場合があります。
Graham Borland 2013

8
数セント追加するだけです...カーソルを反復処理する前にmoveToFirst()を呼び出してカーソルにデータがあるかどうかを確認しないでください。最初のエントリが失われます
AAverin

47
を使用する場合は、画面の向きが変わるとローダーがカーソルを再利用するため、反復する前にCursorLoader必ず呼び出してくださいcursor.moveToPosition(-1)。1時間かけてこの問題を追跡しました。
Vicky Chijwani

111

カーソルを移動するのに最適な方法は次のとおりです。

Cursor cursor;
... //fill the cursor here

for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
    // do what you need with the cursor here
}

後でカーソルを閉じることを忘れないでください

編集:あなたが責任のないカーソルを反復する必要がある場合、与えられた解決策は素晴らしいです。良い例は、メソッドの引数としてカーソルを使用していて、カーソルの現在の位置を気にすることなく、カーソルをスキャンして特定の値を取得する必要がある場合です。


8
1つだけで実行できるのに、なぜ3つの異なるメソッドを呼び出すのですか?なぜそれが良いと思いますか?
Graham Borland

11
これは、既存のカーソルを再ロードしていて、反復が最初から始まることを確認したい場合に最も安全な方法です。
Michael Eilers Smith 2013

9
単純な代替案よりも明確であることに同意します。私は一般的に、簡潔さよりも明快さを優先します。whileループと同様の変化- android.codota.com/scenarios/51891850da0a87eb5be3cc22/...
drorw

カーソルでもう少し遊んだ後、私は私の答えを更新しました。与えられたコードは確かにカーソルを反復する最も効率的な方法ではありませんが、それはその使用法を持っています。(編集を参照)
Alex Styl 2014

@AlexStylはい、本当にそうです!これは私の正気を救った!
アレッサンドロ

45

カーソルが開始位置にない場合にも機能する3番目の選択肢を指摘したいと思います。

if (cursor.moveToFirst()) {
    do {
        // do what you need with the cursor here
    } while (cursor.moveToNext());
}

1
冗長チェックがあります。if + do-whileを、受け入れられたソリューションで提供されているsimple whileに置き換えることができます。
mtk 2014

6
@mtkいいえ、それは冗長ではありません、それがポイントです-カーソルが再利用されている場合、それはある位置にある可能性があるため、明示的にmoveToFirstを呼び出す必要があります
Mike Repass

これは、ロギングを含むelseステートメントがある場合にのみ役立ちます。そうでなければ、グラハム・ボーランドの答えはより簡潔です。
RDS

5
以下のようヴィッキーChijwaniのコメントが指摘し、現実世界の使用でグラハム・ボーランドの答えは安全でないと必要ですmoveToPosition(-1)。したがって、どちらも2つのカーソル呼び出しがあるため、どちらの回答も同じように簡潔になります。この回答は、-1マジックナンバーを必要としないため、ボーランドの回答を完全に覆い尽くしていると思います。
ベンジャミン

実際には、カーソルが値を持たないことを知りたい場合に便利です。ここにifステートメントにelseを追加するのは簡単で理にかなっているからです。
chacham15 2015年

9

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;
    }
}
}

これはかなりクールなソリューションですがCursor、ループの各反復で同じインスタンスを使用しているという事実を隠しています。
npace 2016年

8

以下はより良い方法です:

if (cursor.moveToFirst()) {
   while (!cursor.isAfterLast()) {
         //your code to implement
         cursor.moveToNext();
    }
}
cursor.close();

上記のコードは、それが反復全体を通過し、最初と最後の反復をエスケープしないことを保証します。


6
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();
}

素敵でコンパクトな実装のための+1。バグ修正:MyInterfaceがデータベースプロパティのゲッターを定義するクラスを受け取る、私が使用するiterator()再計算も必要です。これにより、カーソルベースのIterator <MyInterface>toVisit = cursor.getCount();class IterableCursor<T extends Cursor> implements Iterable<T>, Iterator<T> {...extends CursorWrapper implements MyInterface
k3b

4

Do / Whileソリューションはより洗練されていますが、上記のWhileソリューションのみを使用し、moveToPosition(-1)を使用しない場合、最初の要素が失われます(少なくともContactクエリでは)。

私は提案します:

if (cursor.getCount() > 0) {
    cursor.moveToPosition(-1);
    while (cursor.moveToNext()) {
          <do stuff>
    }
}

2
if (cursor.getCount() == 0)
  return;

cursor.moveToFirst();

while (!cursor.isAfterLast())
{
  // do something
  cursor.moveToNext();
}

cursor.close();

2

カーソルが表すインタフェースであります2-dimensional、任意のデータベースのテーブル。

SELECTステートメントを使用してデータを取得しようとすると、データベースは最初にCURSORオブジェクトを作成しますをその参照を返します。

この返された参照のポインタは0番目の場所を指しています、カーソルの最初の場所の前と同じように呼び出されるをため、カーソルからデータを取得する場合は、最初に1番目のレコードに移動して、 moveToFirst

CursorでmoveToFirst()メソッドを呼び出すと、カーソルポインタが最初の場所に移動します。これで、最初のレコードにあるデータにアクセスできます

見るための最良の方法:

カーソルカーソル

for (cursor.moveToFirst(); 
     !cursor.isAfterLast();  
     cursor.moveToNext()) {
                  .........
     }

0

当初は、カーソル使用して、最初の行のショーにないmoveToNext()レコードは、それを存在していないときにカーソルを繰り返すことができreturn false、それがない限りreturn true

while (cursor.moveToNext()) {
    ...
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.