在Java中以相反的顺序遍历列表


251

我正在迁移一段代码以利用泛型。这样做的一个论据是for循环比跟踪索引或使用显式迭代器要干净得多。

在大约一半的情况下,今天使用索引以相反的顺序迭代列表(ArrayList)。

有人能建议一种更干净的方法(因为我不喜欢indexed for loop使用集合时),尽管它确实可以工作?

 for (int i = nodes.size() - 1; i >= 0; i--) {
    final Node each = (Node) nodes.get(i);
    ...
 }

注意:我无法在JDK之外添加任何新的依赖项。


10
使用显式索引遍历索引数据结构有什么不好?至少它告诉您发生了什么事。为了向后迭代,我总是使用以下简短的成语:for (int i = nodes.size(); --i >= 0;)
x4u 2010年

4
没什么特别的,我只是想对接口编程,而不知道我使用的是哪种列表。虽然我非常喜欢你的短手。(+1评论)
Allain Lalonde 2010年

1
@ x4u:尽管Iterator可以快速进行故障转移,并且在迭代过程中可以轻松删除元素,但其中没有太多内容。
Adamski 2010年

2
此类已损坏,因为用户可能想在同一Iterable上进行第二次迭代,或者列表可能在构造iterable的时间与迭代时间之间改变。我敢肯定,出于您的目的,您只会确保不这样做,但是修复代码并不难。或只是窃取从番石榴(Apache 2.0许可)的代码: code.google.com/p/guava-libraries/source/browse/trunk/src/com/...
凯文Bourrillion

1
足够公平,但是如果我没看错的话,即使是番石榴也容易受到同一事物的影响。如果用户保留结果的反向副本,则会遇到同样的问题。
Allain Lalonde

Answers:


446

试试这个:

// Substitute appropriate type.
ArrayList<...> a = new ArrayList<...>();

// Add elements to list.

// Generate an iterator. Start just after the last element.
ListIterator li = a.listIterator(a.size());

// Iterate in reverse.
while(li.hasPrevious()) {
  System.out.println(li.previous());
}

4
不错。不使用索引,但是失去了每种语法的优雅。还是+1。
Allain Lalonde 2010年

26
调用没有索引参数的listIterator()将在列表的开头给出一个Iterator,因此hasPrevious()在第一次调用时将返回false。
亚当斯基(Adamski)2010年

2
我想您要在listIterator通话中建立索引。
Tom Hawtin-大头钉

1
您可以编写一个反向Iterator使用a ListIterator的变量,但是对于一个循环而言可能不值得。
Tom Hawtin-大头钉

1
这不是一个循环,因此我已将其包装。pastebin.ca/1759041所以,现在我可以了for (Node each : new ListReverse<Node>(nodes)) { }
Allain Lalonde 2010年

35

番石榴提供Lists#reverse(List)ImmutableList#reverse()。与Guava的大多数情况一样,如果参数为an,则前者将委派给后者ImmutableList,因此您可以在所有情况下使用前者。这些不会创建列表的新副本,而只是创建列表的“反向视图”。

List reversed = ImmutableList.copyOf(myList).reverse();

23

我认为使用for循环语法是不可能的。我唯一可以建议的是做类似的事情:

Collections.reverse(list);
for (Object o : list) {
  ...
}

...但是考虑到效率会降低,我不会说这是“更清洁”的方法。


26
并且也会改变您遍历的列表的位置,这是一个很大的副作用。(假设您将其包装在一个方法中,每次调用它时,您都
将以

这是最干净的解决方案。
jfajunior

14

选项1:您是否考虑过用Collections#reverse()反转列表,然后使用foreach?

当然,您可能还需要重构代码,以使列表正确排序,因此您不必进行反向操作,这会占用额外的空间/时间。


编辑:

选项2:或者,您可以使用Deque代替ArrayList吗?它将允许您前后迭代


编辑:

选项3:正如其他人所建议的那样,您可以编写一个Iterator,它将以相反的顺序遍历列表,这是一个示例:

import java.util.Iterator;
import java.util.List;

public class ReverseIterator<T> implements Iterator<T>, Iterable<T> {

    private final List<T> list;
    private int position;

    public ReverseIterator(List<T> list) {
        this.list = list;
        this.position = list.size() - 1;
    }

    @Override
    public Iterator<T> iterator() {
        return this;
    }

    @Override
    public boolean hasNext() {
        return position >= 0;
    }

    @Override
    public T next() {
        return list.get(position--);
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException();
    }

}


List<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");

for (String s : new ReverseIterator<String>(list)) {
    System.out.println(s);
}

1
已经考虑过了,但是逆转它的成本实在让人望而却步。而且,它只需要大约一半时间的这种迭代。翻转它只会将问题移到另一半。
阿兰·拉隆德

3
+代表双端队列;典型的实现有descendingIterator()
垃圾神

这对于链接列表来说会很糟糕,OP的变体更好。
masterxilo 2014年

1
for each我认为使用表达式是最惯用的解决方案。很高兴认识到,如果您的List以向后迭代的方式实现Iterable,则可以实现。我将使用这种方法,并使用Apache Commons Collections中的ReverseListIterator类。
David Groomes 2014年

11

您可以使用具体的类LinkedList而不是常规接口List。然后您有一个descendingIterator用于反向的迭代。

LinkedList<String > linkedList;
for( Iterator<String > it = linkedList.descendingIterator(); it.hasNext(); ) {
    String text = it.next();
}

不知道为什么没有 descendingIteratorArrayList


9

这是一个古老的问题,但是缺少java8友好的答案。在Streaming API的帮助下,以下是一些反向迭代列表的方法:

List<Integer> list = new ArrayList<Integer>(Arrays.asList(1, 3, 3, 7, 5));
list.stream().forEach(System.out::println); // 1 3 3 7 5

int size = list.size();

ListIterator<Integer> it = list.listIterator(size);
Stream.generate(it::previous).limit(size)
    .forEach(System.out::println); // 5 7 3 3 1

ListIterator<Integer> it2 = list.listIterator(size);
Stream.iterate(it2.previous(), i -> it2.previous()).limit(size)
    .forEach(System.out::println); // 5 7 3 3 1

// If list is RandomAccess (i.e. an ArrayList)
IntStream.range(0, size).map(i -> size - i - 1).map(list::get)
    .forEach(System.out::println); // 5 7 3 3 1

// If list is RandomAccess (i.e. an ArrayList), less efficient due to sorting
IntStream.range(0, size).boxed().sorted(Comparator.reverseOrder())
    .map(list::get).forEach(System.out::println); // 5 7 3 3 1

2
非常好,只是外观上的更改:int size = list.size(); ListIterator <Integer>它= list.listIterator(size); Stream.generate(it :: previous).limit(size).forEach(System.out :: println);
benez '16

5

这是的(未试用)实现ReverseIterable。当iterator()被称为创建并返回一个私有ReverseIterator的实现,它简单地映射到呼叫hasNext()hasPrevious()和调用next()映射到previous()。这意味着您可以ArrayList按如下所示反向迭代:

ArrayList<String> l = ...
for (String s : new ReverseIterable(l)) {
  System.err.println(s);
}

类定义

public class ReverseIterable<T> implements Iterable<T> {
  private static class ReverseIterator<T> implements Iterator {
    private final ListIterator<T> it;

    public boolean hasNext() {
      return it.hasPrevious();
    }

    public T next() {
      return it.previous();
    }

    public void remove() {
      it.remove();
    }
  }

  private final ArrayList<T> l;

  public ReverseIterable(ArrayList<T> l) {
    this.l = l;
  }

  public Iterator<T> iterator() {
    return new ReverseIterator(l.listIterator(l.size()));
  }
}

在我看来不错,尽管将其作为静态方法而不是公共构造函数公开(在大多数情况下)将避免客户端指定类型参数的需要。这是番石榴是如何做的..
凯文Bourrillion

2
这是最好的实现,但是ReverseIterator缺少必需的构造函数,因此代码应使用List代替ArrayList
安迪

5

如果列表非常小,以至于性能不是真正的问题,则可以在中使用-class 的reverse-metod 。产生漂亮的代码,并且原始列表保持不变。同样,反向列表由原始列表支持,因此对原始列表的任何更改都将反映在反向列表中。ListsGoogle Guavafor-each

import com.google.common.collect.Lists;

[...]

final List<String> myList = Lists.newArrayList("one", "two", "three");
final List<String> myReverseList = Lists.reverse(myList);

System.out.println(myList);
System.out.println(myReverseList);

myList.add("four");

System.out.println(myList);
System.out.println(myReverseList);

产生以下结果:

[one, two, three]
[three, two, one]
[one, two, three, four]
[four, three, two, one]

这意味着myList的反向迭代可以写为:

for (final String someString : Lists.reverse(myList)) {
    //do something
}

5

创建一个自定义reverseIterable


不知道为什么这被否决了,我可能只是为执行此操作的列表做一个包装。
Allain Lalonde 2010年

这和调用Collections.reverse()恕我直言一样干净。
Adamski

@Allain:同意。尽管我不明白为什么您将对listIterator(list.size())的调用视为“不干净”,但仍令人失望。即使将其包装,您仍然必须在某个地方进行相同的方法调用。
亚当斯基2010年

假设不是,只是不愿意因为性能下降而受到性能影响。
阿兰·拉隆德

18
投票否决了,因为我认为这不是一个完整的答案。
Maarten Bodewes 2014年

3

很简单的例子:

List<String> list = new ArrayList<String>();

list.add("ravi");

list.add("kant");

list.add("soni");

// Iterate to disply : result will be as ---     ravi kant soni

for (String name : list) {
  ...
}

//Now call this method

Collections.reverse(list);

// iterate and print index wise : result will be as ---     soni kant ravi

for (String name : list) {
  ...
}



1

要获得如下所示的代码:

List<Item> items;
...
for (Item item : In.reverse(items))
{
    ...
}

将此代码放入一个名为“ In.java”的文件中:

import java.util.*;

public enum In {;
    public static final <T> Iterable<T> reverse(final List<T> list) {
        return new ListReverseIterable<T>(list);
    }

    class ListReverseIterable<T> implements Iterable<T> {
        private final List<T> mList;

        public ListReverseIterable(final List<T> list) {
            mList = list;
        }

        public Iterator<T> iterator() {
            return new Iterator<T>() {
                final ListIterator<T> it = mList.listIterator(mList.size());

                public boolean hasNext() {
                    return it.hasPrevious();
                }
                public T next() {
                    return it.previous();
                }
                public void remove() {
                    it.remove();
                }
            };
        }
    }
}

1
线程的其他地方已经提到了这一点,但是该listIterator字段必须位于Iterator实现内部,而不是Iterable实现内部。
安迪

1
为什么使用枚举类型而不是类?
奥宾2014年

1
它使'In'类不可实例化,而不必编写私有的默认构造函数。适用于仅具有静态方法的类。
intrepidis 2014年

我会说,为了避免编写私有默认构造函数而滥用枚举充其量是令人困惑的。如何将枚举用于枚举,将类用作类?通过将一个类变成一个枚举,它还隐式地获得了一个name()方法,一个ordinal()方法和一个static valueOf()方法。
JHH

1
是的,您可以继续发表您的意见,也可以很好地工作,但是我认为恰恰相反。类用于实例化对象并通过包含私有默认构造函数禁止其实例化来滥用它们,这充其量是令人困惑的。在Java中,枚举实际上是类,但不能通过设计实例化。
intrepidis

0

正如至少已经提出了两次,就可以使用descendingIterator同一个Deque,特别是一个LinkedList。如果要使用for-each循环(即具有Iterable),则可以构造和使用如下包装器:

import java.util.*;

public class Main {

    public static class ReverseIterating<T> implements Iterable<T> {
        private final LinkedList<T> list;

        public ReverseIterating(LinkedList<T> list) {
            this.list = list;
        }

        @Override
        public Iterator<T> iterator() {
            return list.descendingIterator();
        }
    }

    public static void main(String... args) {
        LinkedList<String> list = new LinkedList<String>();
        list.add("A");
        list.add("B");
        list.add("C");
        list.add("D");
        list.add("E");

        for (String s : new ReverseIterating<String>(list)) {
            System.out.println(s);
        }
    }
}

-10

原因:“不知道为什么没有ArrayList的DescendingIterator ...”

由于数组列表的顺序与数据添加到列表的顺序不同。因此,永远不要使用Arraylist。

链接列表将使数据按添加到列表的相同顺序保留。

因此,在上面的示例中,我使用ArrayList()来使用户改变主意并使其从侧面锻炼某些东西。

代替这个

List<String> list = new ArrayList<String>();

用:

List<String> list = new LinkedList<String>();

list.add("ravi");

list.add("kant");

list.add("soni");

// Iterate to disply : result will be as ---     ravi kant soni

for (String name : list) {
  ...
}

//Now call this method

Collections.reverse(list);

// iterate and print index wise : result will be as ---     soni kant ravi

for (String name : list) {
  ...
}

4
“因为数组列表不会按照将数据添加到列表中的顺序来保持列表的顺序”,嗯,是的,至少它的工作方式与链接列表相同。您能否提供一个例子,说明他们没有这样做?
corsiKa 2013年

是的,ArrayList和LinkedList(通常为List合同)使项目按顺序插入。Set合约是无序的或按排序排序的(TreeSet)。相反的想法并不坏,但请记住,它实际上对列表进行了重新排序,这可能会变慢。
Bill K

2
我对这个答案不满意,因为它错误地指出ArrayLists不会按插入顺序保留项目。如@ bill-k所述,所有列表都是按定义排序的集合。使用LinkedList的建议具有优点,因为它具有下降的Iterator()方法,但前提是性能比内存和抽象更重要。
Michael Scheper
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.