可以以相反的顺序为Java中的每个循环做一个吗?


148

我需要使用Java以相反的顺序遍历一个List。

所以它转发到哪里:

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

是否有某种方法可以针对每种语法以相反的顺序迭代stringList ?

为了清楚起见:我知道如何以相反的顺序迭代列表,但想知道(出于好奇的缘故)如何针对每种样式进行处理。


5
“ for-each”循环的要点是,您只需要对每个元素执行一个操作,并且顺序并不重要。For-each可以完全随机的顺序处理元素,并且仍会按照设计的目的进行操作。如果您需要以特定方式处理元素,建议您手动进行。
muusbolla

Java集合库。与语言无关。怪约什·布洛赫(Josh Bloch)。
汤姆·霍顿

7
@muusbolla:但是由于List是一个有序集合,因此肯定会遵守其顺序,无论如何?因此,for-each不会以随机顺序处理List的元素。
Lee Kowalkowski 2011年

5
@muusbolla是不正确的。也许在Set派生集合的情况下。foreach保证按照从iterator()集合方法返回的迭代器的顺序进行迭代。docs.oracle.com/javase/1.5.0/docs/guide/language/foreach.html
罗伯特

Answers:


151

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

22
基本上,这就是Google的Iterables.reverse所做的,是的:)
乔恩·斯基特

10
我知道有一个“规则”,我们必须接受乔恩的回答:),但是..我想接受这一规则(即使它们本质上是相同的),因为它不需要我包括另一个第三方图书馆(即使有些人可能会说,原因破坏了OO的主要优势之一-可重用性。
罗恩·塔芬

小错误:在public void remove()中,不应有return语句,而应只是:i.remove();。
杰斯珀,

10
Collections.reverse()不会返回反向副本,但会对作为参数传递给它的List起作用。但是,就像使用迭代器的解决方案一样。真的很优雅。
er4z0r 2012年

96

有关列表,您可以使用Google Guava库

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

请注意,这不会颠倒整个集合,也不会做任何类似的事情-它只允许以相反的顺序进行迭代和随机访问。这比先反转集合更有效。Lists.reverse

要撤消任意可迭代项,您必须阅读所有内容,然后向后“重播”。

(如果你还没有使用它,我会彻底建议你看看番石榴。这是伟大的东西。)


1
我们的代码库大量使用了由larvalabs(larvalabs.com/collections)发布的Commons Collections的通用版本。查看Apache Commons的SVN存储库,很明显,发布Java 5版本的Commons Collections的大部分工作已经完成,但他们还没有发布。
skaffman

我喜欢。如果不是那么有用,我会打电话给它。
geowa4,2009年

我想知道为什么雅加达从不费心去更新Apache Commons。
Uri

3
他们已经更新了,这就是我的意思。他们只是还没有发布它。
skaffman

23
Iterables.reverse已被弃用,请改用Lists.reverse或ImmutableList.reverse。
加勒特音乐厅

39

List(与Set不同)是一个有序的集合,并且对其进行迭代确实会按合同保留订单。我本来希望Stack以相反的顺序进行迭代,但是不幸的是它没有。因此,我能想到的最简单的解决方案是:

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

我意识到这不是一个“针对每个”循环的解决方案。我宁愿使用for循环,也不愿引入像Google Collections这样的新库。

Collections.reverse()也可以完成此工作,但它会更新列表,而不是以相反的顺序返回副本。


3
这种方法对于基于数组的列表(例如ArrayList)可能很好,但是对于链接列表,它不是次优的,因为每个获取都必须从每个获取开始到结束(或可能从开始到结束)遍历列表。最好在Nat的解决方案中使用更智能的迭代器(对于List的所有实现都是最佳的)。
克里斯,

1
此外,从OP的要求,其中明确要求对流浪狗for each的语法
保瓦

8

这将与原始列表混淆,还需要在循环外部调用。另外,您也不希望每次循环都执行反向操作-如果应用了其中之一,那会是真的Iterables.reverse ideas吗?

Collections.reverse(stringList);

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

5

AFAIK在标准库中没有标准的“ reverse_iterator”类东西支持for-each语法,这已经是他们后来引入该语言的语法糖。

您可以执行类似for(Item element:myList.clone()。reverse())的操作并支付相关价格。

这似乎也与没有给您方便的方法进行昂贵的操作的明显现象相符-因为按照定义,列表可能具有O(N)随机访问的复杂性(您可以使用单链接实现接口),反向迭代最终可能是O(N ^ 2)。当然,如果您有ArrayList,则无需支付该价格。


您可以向后运行ListIterator,可以将其包装在Iterator中。
Tom Hawtin-抢险

@汤姆:好点。但是,使用迭代器,您仍在执行烦人的旧式for循环,并且仍然可能要花钱才能到达最后一个开始的元素……不过,我在回答中添加了限定符,谢谢。
乌里(Uri)

Deque具有反向迭代器。
Michael Munsey 2010年

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

2

截至评论:您应该能够使用Apache CommonsReverseListIterator

Iterable<String> reverse 
    = new IteratorIterable(new ReverseListIterator(stringList));

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

正如@rogerdpack所说,您需要将包裹ReverseListIterator为一个Iterable


1

并非没有编写一些自定义代码,这将为您提供一个枚举器,它将为您反转元素。

您应该能够通过创建Iterable的自定义实现来用Java做到这一点,该实现将以相反的顺序返回元素。

然后,您将实例化包装器(或调用该方法,即您拥有),该包装器将返回Iterable实现,该实现会反转for每个循环中的元素。



1

如果要对每种语法使用开箱即用的顺序并以相反的顺序进行,则需要反转集合。


1

以上所有答案都只能通过包装另一种方法或在外部调用一些外部代码来满足要求。

下面是从复制的解决方案中的Java第四版思考,章11.13.1 AdapterMethodIdiom ;

这是代码:

// The "Adapter Method" idiom allows you to use foreach
// with additional kinds of Iterables.
package holding;
import java.util.*;

@SuppressWarnings("serial")
class ReversibleArrayList<T> extends ArrayList<T> {
  public ReversibleArrayList(Collection<T> c) { super(c); }
  public Iterable<T> reversed() {
    return new Iterable<T>() {
      public Iterator<T> iterator() {
        return new Iterator<T>() {
          int current = size() - 1; //why this.size() or super.size() wrong?
          public boolean hasNext() { return current > -1; }
          public T next() { return get(current--); }
          public void remove() { // Not implemented
            throw new UnsupportedOperationException();
          }
        };
      }
    };
  }
}   

public class AdapterMethodIdiom {
  public static void main(String[] args) {
    ReversibleArrayList<String> ral =
      new ReversibleArrayList<String>(
        Arrays.asList("To be or not to be".split(" ")));
    // Grabs the ordinary iterator via iterator():
    for(String s : ral)
      System.out.print(s + " ");
    System.out.println();
    // Hand it the Iterable of your choice
    for(String s : ral.reversed())
      System.out.print(s + " ");
  }
} /* Output:
To be or not to be
be to not or be To
*///:~

为什么是int current = size() - 1正确的?为什么不int current = this.size() - 1int current = super.size() - 1
启稳利

1

解决方法:

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

或搭配番石榴

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

0

绝对是这个问题的最新答案。一种可能性是在for循环中使用ListIterator。它不像冒号语法那么干净,但是可以工作。

List<String> exampleList = new ArrayList<>();
exampleList.add("One");
exampleList.add("Two");
exampleList.add("Three");

//Forward iteration
for (String currentString : exampleList) {
    System.out.println(currentString); 
}

//Reverse iteration
for (ListIterator<String> itr = exampleList.listIterator(exampleList.size()); itr.hasPrevious(); /*no-op*/ ) {
    String currentString = itr.previous();
    System.out.println(currentString); 
}

ListIterator语法的功劳是“迭代Java列表的方式”

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.