【问题标题】:Using ListIterator to move back and forth over a LinkedList in Java使用 ListIterator 在 Java 中的 LinkedList 上来回移动
【发布时间】:2012-11-09 02:24:46
【问题描述】:

我有一个 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

我想我可能是错误地构建了我的列表,或者使用了错误的迭代器,但是在阅读了文档之后,这似乎是设计使然:

ListIterator 没有当前元素;它的光标位置始终位于调用 previous() 将返回的元素与调用 next() 将返回的元素之间。

还有:

(Next) 返回列表中的下一个元素。可以重复调用此方法以遍历列表,或与调用 previous 混合调用以来回移动。 (请注意,交替调用 next 和 previous 将重复返回相同的元素。)

因此,阅读本文后,很清楚为什么我的代码会如此行事。我只是不明白为什么它应该这样工作。甚至 remove 似乎也在向后弯曲以适应这种实现:

请注意,remove() 和 set(Object) 方法不是根据光标位置定义的;它们被定义为对调用 next() 或 previous() 返回的最后一个元素进行操作。

从概念上讲,LinkedList 似乎很好地模拟了我的工作流案例,但我不能使用以这种方式运行的迭代器。我是否在这里遗漏了什么,或者我应该只编写自己的课程来维护一个案例列表并浏览它们?

【问题讨论】:

  • 为什么你不能“使用这样的迭代器”?世界其他地方也是如此。只需接受它的工作原理并按照预期使用它。
  • 之所以如此,是因为它使从头开始的迭代和删除与从头开始的迭代和删除的工作方式完全相同。
  • 如果必须,将核心功能包装在您自己的逻辑中,当您请求 myIterator.previous() 时调用 iterator.previous().previous()(显然是伪)
  • @Madbreaks 当上一个操作是 next() 时,您只需调用 previous 两次。否则你只调用一次。因此我很沮丧:)
  • 这是非常令人沮丧的行为。我花了几个小时来重现和调试包含此代码的问题。

标签: java iterator linked-list


【解决方案1】:

这应该可以完成您的工作:

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

    }   
}

还有一个fiddle

【讨论】:

  • 是的,我看不出有什么办法可以不必记住之前的操作。谢谢!
【解决方案2】:

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()

【讨论】:

    【解决方案3】:

    做这样的事情(伪代码)--

    class SkipIterator extends ListIterator {
    
        public E previous(){
            E n = super.previous();
            return super.previous();
        }
    
        ...
    
    }
    

    然后:

    LinkedList<String> navigationCases;
    navigationCases.add("page1");
    navigationCases.add("page2");
    navigationCases.add("page3");
    navigationCases.add("page4");
    
    SkipIterator navigationItr = (SkipIterator)navigationCases.listIterator();
    navigationItr.next(); // Returns page1
    navigationItr.next(); // Returns page2
    navigationItr.previous(); // Returns page1
    

    干杯

    【讨论】:

    • 不幸的是,如果之前的操作是 next(),我只想调用 previous() 两次。如果 next 之前没有调用过,那么调用 previous() 两次实际上会导致迭代器跳过列表中的一个元素。
    • 好的,所以构建该逻辑。:) 想法是您可以扩展基类并包含您的用例所需的逻辑。
    • 在没有事先调用 next() 的情况下向后导航时,每次调用执行两次 previous() 不会给出所需的行为。
    猜你喜欢
    • 2017-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-19
    • 1970-01-01
    • 1970-01-01
    • 2019-12-23
    相关资源
    最近更新 更多