在Java中,能否以相反的顺序执行for each循环?

172

我需要使用Java倒序遍历一个List。

所以这个代码是正向遍历:

for(String string: stringList){
//...do something
}

有没有办法使用 for each 语法以相反的顺序遍历stringList?

为了明确起见:我知道如何按相反顺序迭代列表,但是想知道(出于好奇),如何以 for each 样式执行此操作。


8
“for-each” 循环的重点在于你只需要对每个元素执行一次操作,顺序并不重要。for-each 可以按照完全随机的顺序处理元素,但仍然能够完成其设计目的。如果您需要以特定方式处理元素,则建议手动处理。 - muusbolla
1
Java集合库。实际上与语言本身没有太大关系。这要怪Josh Bloch。 - Tom Hawtin - tackline
9
但是由于List是一个有序的集合,所以它的顺序肯定会被尊重,因此for-each循环不会随机处理List中的元素。 - Lee Kowalkowski
8
@muusbolla那不是真的。也许在派生自Set的集合中是这样。foreach保证按照从集合的iterator()方法返回的迭代器的顺序进行迭代。http://docs.oracle.com/javase/1.5.0/docs/guide/language/foreach.html - robert
我也希望Java中也有这个内置功能。在Python中,我们可以编写for row in rows[::-1]来进行反向迭代。为什么Java没有呢? - Zhou Haibo
15个回答

159

Collections.reverse方法实际上返回了一个包含原始列表中元素的新列表,这些元素是按相反顺序复制到其中的,因此其性能与原始列表的大小成O(n)比例。

作为更有效的解决方案,你可以编写一个装饰器,将List的反向视图呈现为Iterable。装饰器返回的迭代器将使用装饰列表的ListIterator以相反的顺序遍历元素。

例如:

public class Reversed<T> implements Iterable<T> {
    private final List<T> original;

    public Reversed(List<T> original) {
        this.original = original;
    }

    public Iterator<T> iterator() {
        final ListIterator<T> i = original.listIterator(original.size());

        return new Iterator<T>() {
            public boolean hasNext() { return i.hasPrevious(); }
            public T next() { return i.previous(); }
            public void remove() { i.remove(); }
        };
    }

    public static <T> Reversed<T> reversed(List<T> original) {
        return new Reversed<T>(original);
    }
}

你可以这样使用它:

import static Reversed.reversed;

...

List<String> someStrings = getSomeStrings();
for (String s : reversed(someStrings)) {
    doSomethingWith(s);
}

23
基本上,这就是Google的Iterables.reverse所做的事情,是的 :) - Jon Skeet
10
我知道有一条“规则”必须接受Jon的答案 :),但是...我想接受这个(尽管它们本质上相同)因为它不需要我包含另一个第三方库(尽管有人可能会争论这个原因违反了面向对象编程的一个主要优势-可重用性)。 - Ron Tuffin
小错误:在 public void remove() 中,不应该有返回语句,而应该只是 i.remove(); - Jesper
12
Collections.reverse() 不会返回一个反转后的副本,而是作用于作为参数传递给它的 List 上。不过,你通过迭代器解决这个问题的方法真的很优雅。 - er4z0r

101

你可以使用Google Guava库来处理列表:

for (String item : Lists.reverse(stringList))
{
    // ...
}

请注意,Lists.reverse 不会 翻转整个集合或类似的操作——它只允许按相反顺序进行迭代和随机访问,这比先翻转整个集合更有效率。

要翻转任意iterable对象,您需要读取所有内容,然后以相反顺序“重放”。

(如果您还没有使用它,我强烈建议您看一下Guava。它是非常好的东西。)


1
我们的代码库大量使用了larvalabs发布的Commons Collections的泛型版本(http://larvalabs.com/collections/)。通过查看Apache Commons的SVN仓库,很明显发布Java 5版本的Commons Collections的大部分工作已经完成,只是他们还没有发布它。 - skaffman
3
他们已经更新了它,这就是我在说的。他们只是还没有发布它。 - skaffman
23
Iterables.reverse 已被弃用,请使用 Lists.reverse 或 ImmutableList.reverse 代替。 - Garrett Hall
看起来 Guava(19.0)不再有 Reverse 的任何实现。是否有其他替代 Lists.Reverse 的方法? - AaA
此外,您可以使用Guava的Lists.reverse与Arrays.asList(数组上的非复制List包装器)结合使用来迭代数组,或者对于原始类型,使用各种Guava asList方法,例如Ints.asList(尽管在后一种情况下,您将浪费地进行每个基元的装箱和拆箱)。 - laszlok
显示剩余6条评论

46

列表(与集合不同)是一个有序的集合,迭代它会按照契约保留顺序。我本来期望栈可以按照相反的顺序进行迭代,但很遗憾它没有这样做。因此,我能想到的最简单的解决方案是:

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

我意识到这不是“for each”循环的解决方案。我宁愿使用for循环,而不是引入像Google Collections这样的新库。

Collections.reverse()也可以完成任务,但它会更新列表,而不是返回一个以相反顺序排列的副本。


4
这种方法对于基于数组的列表(例如ArrayList)可能还可以,但对于链表来说会不够优秀,因为每次获取数据都需要从头到尾(或者可能从尾到头)遍历整个链表。更好的做法是使用像Nat的解决方案中所使用的更智能的迭代器(适用于所有List实现的最佳选择)。 - Chris
1
此外,它偏离了OP中的请求,该请求明确要求使用“for each”语法。 - Paul W
更好的可读性:for (int i = stack.size(); i-- >0;) { - j-hap

11

这将干扰原始列表并且需要在循环外调用。此外,您不希望每次循环都执行反转-如果应用了Iterables.reverse ideas中的一个,这是否正确?

Collections.reverse(stringList);

for(String string: stringList){
//...do something
}

6
据我所知,在标准库中没有支持for-each语法的标准“reverse_iterator”排序方式。你可以尝试使用类似于"for(Item element: myList.clone().reverse())"的方法,但是需要承担相应的代价。
这也似乎与不提供方便的操作方式的现象相当一致 - 因为按定义,列表可能具有O(N)的随机访问复杂度(你可以用单链实现接口),反向迭代可能会变成O(N^2)。当然,如果你有一个ArrayList,你就不需要支付这个代价。

你可以反向运行ListIterator,这可以包装在Iterator中。 - Tom Hawtin - tackline
@Tom:说得好。然而,使用迭代器仍然需要使用繁琐的旧式for循环,并且你可能仍然需要付出获取最后一个元素的代价...不过我在我的回答中添加了限定词,谢谢。 - Uri
Deque有一个反向迭代器。 - Michael Munsey

3

1
在后续的Java版本中,您可以使用以下简写方式: Iterable<String> reverse = () -> new ReverseListIterator<>(stringList) - Roland Nordborg-Løvstad

2
这可能是一个选项。希望有更好的方法从最后一个元素开始而不是通过while循环到结尾。
public static void main(String[] args) {        
    List<String> a = new ArrayList<String>();
    a.add("1");a.add("2");a.add("3");a.add("4");a.add("5");

    ListIterator<String> aIter=a.listIterator();        
    while(aIter.hasNext()) aIter.next();

    for (;aIter.hasPrevious();)
    {
        String aVal = aIter.previous();
        System.out.println(aVal);           
    }
}

更好的方式是 a.listIterator(a.size()) - David Conrad

2
一个解决方法:
Collections.reverse(stringList).forEach(str -> ...);

或者使用guava

Lists.reverse(stringList).forEach(str -> ...);

1
不写一些自定义代码是无法实现的,该代码将为您提供一个枚举器,以便为您反转元素。
在Java中,您应该能够通过创建Iterable的自定义实现来按相反的顺序返回元素。
然后,您将实例化包装器(或调用方法等),该包装器将返回可迭代的实现,该实现将在for循环中反转元素。

1
你可以使用 Collections 类来反转列表,然后循环。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接