web-dev-qa-db-ja.com

ListIteratorを使用して、Java)のLinkedList上を前後に移動します

LinkedListがあり、これを何度も繰り返す必要があります。動的に作成されるワークフロー内の一連のページを追跡するために使用しています。これは私が期待するように動作しません。この例を考えると:

LinkedList<String> navigationCases;
navigationCases.add("page1");
navigationCases.add("page2");
navigationCases.add("page3");
navigationCases.add("page4");

ListIterator navigationItr = navigationCases.listIterator();
navigationItr.next(); // Returns page1
navigationItr.next(); // Returns page2
navigationItr.previous(); //Returns page2 again
navigationItr.next(); //Returns page2 again

リストを間違って作成しているか、Iteratorを間違って使用しているのではないかと思いましたが、ドキュメントを読んだ後、これは仕様によるもののようです。

ListIteratorには現在の要素がありません。そのカーソル位置は、previous()の呼び出しによって返される要素とnext()の呼び出しによって返される要素の間に常にあります。

そして:

(Next)リスト内の次の要素を返します。このメソッドを繰り返し呼び出してリストを反復処理するか、previousの呼び出しと組み合わせて前後に移動することができます(nextとpreviousの呼び出しを交互に繰り返すと同じ要素を繰り返します。)

したがって、これを読んだ後、私のコードがそのように動作している理由は明らかです。なぜこのように機能するのか理解できません。この実装に対応するために、removeでさえ後方に曲がっているようです。

remove()メソッドとset(Object)メソッドは、カーソル位置に関して定義されていないことに注意してください。next()またはprevious()の呼び出しによって返される最後の要素を操作するように定義されています。 ==

概念的には、LinkedListは私のワークフローのケースをかなりうまくモデル化しているように見えましたが、このように動作するIteratorを使用することはできません。ここで何かが足りないのでしょうか、それとも、ケースのリストを維持してそれらをナビゲートする独自のクラスを作成する必要がありますか?

18
Kurt Koller

これはあなたの仕事をするはずです:

public class Main {
    public static void main(String[] args) {
        final LinkedList<String> list = new LinkedList<String> ();

        list.add ("1"); list.add ("2"); list.add ("3"); list.add ("4");

        final MyIterator<String> it = new MyIterator (list.listIterator());

        System.out.println(it.next());
        System.out.println(it.next ());
        System.out.println(it.next ());
        System.out.println(it.previous ());
        System.out.println(it.previous ());
        System.out.println(it.next ());
    }

    public static class MyIterator<T> {

        private final ListIterator<T> listIterator;

        private boolean nextWasCalled = false;
        private boolean previousWasCalled = false;

        public MyIterator(ListIterator<T> listIterator) {
            this.listIterator = listIterator;
        }

        public T next() {
            nextWasCalled = true;
            if (previousWasCalled) {
                previousWasCalled = false;
                listIterator.next ();
            }
            return listIterator.next ();
        }

        public T previous() {
            if (nextWasCalled) {
                listIterator.previous();
                nextWasCalled = false;
            }
            previousWasCalled = true;
            return listIterator.previous();
        }

    }   
}

そして フィドル それのために。

10
ShyJ

ListIteratorは、このように動作するように設計されています。理論的根拠については、ShyJの回答の下にある会話を参照してください。

私はこの振る舞いがばかげたことを超えていることに気づき、代わりに非常に単純な代替案を書きました。 ArrayListsの拡張関数を使用したKotlinコードは次のとおりです。

_class ListIterator<E>(var list: ArrayList<E>) : Iterator<E> {

    private var cursor: Int = 0

    fun replace(newList: ArrayList<E>) {
        list = newList
        cursor = 0
    }

    override fun hasNext(): Boolean {
        return cursor + 1 < list.size
    }

    override fun next(): E {
        cursor++
        return current()
    }

    fun hasPrevious(): Boolean {
        return 0 <= cursor - 1
    }

    fun previous(): E {
        cursor--
        return current()
    }

    fun current(): E {
        return list[cursor]
    }

}

fun <E> ArrayList<E>.listFlippingIterator() = ListIterator(this)
_

削除機能を含めたい場合は、APIを作成して、イテレータが左または右のどちらを削除するかを明示的に指示することを強くお勧めします。これらのメソッドをremoveNext()およびremovePrevious()として定義します。

0
Paul Lammertsma