java.util.List 中缺少递减迭代器

问题描述 投票:0回答:3

LinkedList
可以使用升序或降序迭代器进行迭代,如下所示:

LinkedList<Object> list = new LinkedList<Object>();
   ...
StringJoiner sJ1 = new StringJoiner(" ");
list.iterator().forEachRemaining(a -> sJ1.add(a.toString()));
System.out.println("averse: \n" + sJ1.toString());

StringJoiner sJ2 = new StringJoiner(" ");
list.descendingIterator().forEachRemaining(a -> sJ2.add(a.toString()));
System.out.println("reverse: \n" + sJ2.toString());
averse: 
Hello 2 Chocolate 10
reverse: 
10 Chocolate 2 Hello

但是

descendingIterator
不适用于
List
ArrayList

是否有任何解决方法,或者至少有一个解释,为什么

descendingIterator
不在
List
中?

这个问题类似于可以在java中以相反的顺序执行foreach循环吗?。但所有答案都推荐临时解决方案或第三方库。

是否可以使用流来实现这一点? (不幸的是,我的谷歌搜索仅给出了流的缺乏标准反转如何反转Java 8流并生成值的递减IntStream?)。

正如我所提到的,我的问题与流有关,但是:

  • 使用流只是一种选择;这个问题是关于
    java.util.Iterator
    java.util.List
  • 该问题的答案仅解决了部分情况,但表明缺乏方便的一般逆向。如果我没有误用这些术语,
    List
    是有序集合。 对
    Comparable
    项目进行排序以实现顺序会缩小范围。
java list arraylist java-8 linked-list
3个回答
7
投票

没有充分的理由解释为什么

List
不能有一个降序迭代器。每个
List
都需要支持
ListIterator
,可以使用
hasPrevious()
previous()
方法以相反顺序迭代。不过,这似乎是一个相当不常见的用例。

这里有一个小实用方法,可以将

Iterable
调整为
ListIterator
以相反顺序迭代:

static <T> Iterable<T> descendingIterable(List<? extends T> list) {
    return () -> {
        ListIterator<? extends T> li = list.listIterator(list.size());
        return new Iterator<T>() {
            public boolean hasNext() { return li.hasPrevious(); }
            public T next() { return li.previous(); }
        };
    };
}

您可以使用它来实现您的示例:

List<String> list = Arrays.asList("Hello", "2", "Chocolate", "10");
StringJoiner sj = new StringJoiner(" ");
descendingIterable(list).iterator().forEachRemaining(sj::add);
System.out.println(sj);

10 Chocolate 2 Hello

或者您可以在增强的 for 循环中使用它:

for (String s : descendingIterable(list)) {
    System.out.println(s);
}

10
Chocolate
2
Hello

我最初创建了一个

Iterator
而不是
Iterable
,但后者更有用,因为它可以在增强的 for 循环中使用。

还请注意,这里有一个小问题,即如果

List
可以同时修改,则存在竞争条件。这发生在此处的代码中:

list.listIterator(list.size())

如果可能,您必须在列表上使用外部同步,或者如果列表是

CopyOnWriteArrayList
,则必须先克隆它。有关后者的更多信息,请参阅此答案


4
投票

有关于为什么 List 中缺少 descendingIterator 的解释吗?

迭代列表的效率高度依赖于该列表的实现。
在设计接口时,您通常希望该接口适合其任何可能的实现。

Java 链表是一个双重链表,这意味着每个元素都链接到它之前和之后的元素,因此可以编写高效的升序和降序迭代器。

如果您使用singly链表,则降序迭代它的效率会非常低,您需要遍历整个列表,直到每次迭代的当前索引。

我怀疑正是由于这个原因,语言设计者决定从 List 接口中省略一个降序迭代器。

使用基于索引的方法可以相当轻松地实现高效的 ArrayList 降序迭代器,但在 LinkedList 上使用索引方法会比其首选实现慢得多。

降序迭代方法的一些示例:

for(int i=list.size() -1; i >= 0; i--){
    System.out.println(list.get(i));
}

IntStream.range(0, list.size())
        .map(i-> list.size() - 1 - i)
        .mapToObj(list::get)
        .forEach(System.out::println);

0
投票

从 Java 21 开始,

reversed()
方法可用于从列表中返回反转的
Iterator
。 在
list.reversed().iterator()
可用于
List
和其他
list.descendingIterator()
类型的情况下,
LinkedList
可用于任何
Deque
类型。

List<Object> list = new ArrayList<Object>();
// ...
StringJoiner sJ1 = new StringJoiner(" ");
list.iterator().forEachRemaining(a -> sJ1.add(a.toString()));
System.out.println("averse: \n" + sJ1.toString());

StringJoiner sJ2 = new StringJoiner(" ");
list.reversed().iterator().forEachRemaining(a -> sJ2.add(a.toString()));
System.out.println("reverse: \n" + sJ2.toString());

虽然它没有解释 为什么 没有一致的

descendingIterator
,但 JEP 431:顺序集合 承认这是一个痛点,也是将
reversed()
方法引入 Java 的激励因素。

从头到尾迭代集合的元素是简单且一致的,但逆序迭代则不然。所有这些集合 [(表示具有定义的遇到顺序的元素序列)] 都可以使用

Iterator
、增强的
for
循环、
stream()
toArray()
向前迭代。每种情况下的反向迭代都是不同的。
NavigableSet
提供用于反向迭代的
descendingSet()
视图:

for (var e : navSet.descendingSet())
    process(e);

Deque
则相反
Iterator
:

for (var it = deque.descendingIterator(); it.hasNext();) {
    var e = it.next();
    process(e);
}

List
这样做,但是使用
ListIterator
:

for (var it = list.listIterator(list.size()); it.hasPrevious();) {
    var e = it.previous();
    process(e);
}
最后,

LinkedHashSet
不支持反向迭代。以相反顺序处理
LinkedHashSet
元素的唯一实用方法是将其元素复制到另一个集合中。

© www.soinside.com 2019 - 2024. All rights reserved.