在给定迭代器的情况下使用for-each循环的惯用方法?

时间:2010-10-07 15:15:21

标签: java guava foreach

当增强的for循环(foreach循环)添加到Java时,它可以使用数组或Iterable的目标。

for ( T item : /*T[] or Iterable<? extends T>*/ ) {
    //use item
}

这适用于仅实现一种迭代类型的Collection类,因此只有一个iterator()方法。

但是我发现自己非常沮丧地想要使用Collection类中的非标准迭代器。例如,我最近试图帮助某人使用Deque作为LIFO /堆栈,然后按FIFO顺序打印元素。我不得不这样做:

for (Iterator<T> it = myDeque.descendingIterator(); it.hasNext(); ) {
   T item = it.next();
   //use item
}

我失去了for-each循环的优点。这不仅仅是击键。我不喜欢暴露迭代器,如果我不需要,因为很容易犯两次调用it.next()的错误等等。

理想情况下,我认为for-each循环也应该接受Iterator。但事实并非如此。那么在这些情况下是否存在使用for-each循环的惯用方法?我也很想听听使用像Guava这样的常见集合库的建议。

我能想出的最好的帮助方法/类是:

for ( T item : new Iterable<T>() { public Iterator<T> iterator() { return myDeque.descendingIterator(); } } ) {
    //use item
}

哪个不值得使用。

我很想看到番石榴有Iterables.wrap之类的东西让这个惯用,但没有找到类似的东西。显然,我可以通过类或辅助方法滚动我自己的Iterator包装器。还有其他想法吗?

编辑:作为附注,任何人都可以提供有效理由说明为什么增强型for循环不应该只接受Iterator?这可能会让我对目前的设计感兴趣。

9 个答案:

答案 0 :(得分:26)

为什么增强的for循环不接受迭代器?

我想从各种答案中收集一些可能的原因,为什么for-each循环不会简单地接受迭代器。

  1. 便利性:for-each循环的创建部分是为了方便执行给定集合中每个元素的操作的常见操作。它没有义务或意图替换显式使用迭代器(显然,如果你想删除元素,你需要显式引用迭代器)。
  2. 可读性:for-each循环for ( Row r : table )意味着极为可读,因为“对于表格中的每一行”r“...”。看到for ( Row r : table.backwardsIterator() )打破了这种可读性。
  3. 透明度:如果某个对象既是Iterable 又是Iterator,那么该行为会是什么?虽然很容易制定一致的规则(例如Iterator之前的Iterable),但开发人员的行为将变得不那么透明。此外,必须在编译时检查这一点。
  4. 封装/范围:这是(在我看来)最重要的原因。 for-each循环旨在封装Iterator并将其范围限制为循环。这使得循环以“只读”的方式有两种:它不会暴露迭代器,这意味着没有(容易)有形的东西,其状态通过循环改变了,也不能改变循环中操作数的状态(通过remove()直接与迭代器接口)。自己传递迭代器必然意味着迭代器被暴露,使你失去了循环的那些“只读”属性。

答案 1 :(得分:15)

我可能会做的只是创建一个名为Deques的实用程序类,如果需要,它可以支持此功能,以及其他实用程序。

public class Deques {
  private Deques() {}

  public static <T> Iterable<T> asDescendingIterable(final Deque<T> deque) {
    return new Iterable<T>() {
      public Iterator<T> iterator() {
        return deque.descendingIterator();
      }
    }
  }
}

这是另一种情况,它真的太糟糕了,我们还没有lambdas和方法引用。在Java 8中,您可以编写类似这样的内容,因为方法引用descendingIterator()Iterable的签名匹配:

Deque<String> deque = ...
for (String s : deque::descendingIterator) { ... }

答案 2 :(得分:9)

不是创建descendingIterator,而是编写一个descendingIterable()方法来返回基于deque的降序迭代,这基本上取代了您的匿名类。这对我来说似乎很合理。根据Colin的建议,每次调用自己的descendingIterator方法时,此方法返回的可迭代实现将在原始双端队列上调用iterator()

如果你只有得到了一个迭代器并希望保持这种方式,你必须编写一个Iterable<T>的实现,它包装了迭代器并返回它正好一次,如果多次调用iterator(),则抛出异常。这样可行,但显然很难看。

答案 3 :(得分:4)

Guava用户可以ImmutableList.copyOf(Iterator)安全地将Iterator转换为Iterable。尽管在迭代器上循环似乎很简单,但有人担心foreach会隐藏,而最安全的选择是创建一个像列表一样稳定的数据结构。

Idea Graveyard

中也对此进行了讨论
  

最大的担忧是Iterable通常被认为能够生成多个独立的迭代器。 doc没有说明这一点,但是Collection doc也没有这样说,但我们假设它是迭代器。当这个假设被违反时,我们已经在Google中破产了。

     

最简单的解决方法是ImmutableList.copyOf(Iterator),它非常快速,安全,并提供许多其他优势。

答案 4 :(得分:3)

public class DescendingIterableDequeAdapter<T> implements Iterable<T> {
    private Deque<T> original;

    public DescendingIterableDequeAdapter(Deque<T> original) {
        this.original = original;
    }

    public Iterator<T> iterator() {
         return original.descendingIterator();
    }
}

然后

for (T item : new DescendingIterableDequeAdapter(deque)) {

}

因此,对于每种情况,您都需要一个特殊的适配器。我认为理论上不可能做你想做的事情,因为设施必须知道迭代器返回方法的存在,以便它可以调用它们。

至于你的其他问题 - 我相信因为for-each循环实际上是为了使通用场景更短。并且调用另一个方法会使语法更加冗长。它可以同时支持IterableIterator,但如果传递的对象同时实现了呢? (会很奇怪,但仍有可能)。

答案 5 :(得分:3)

Java 8中的惯用方式(是一种冗长的语言)是这样的:

for (T t : (Iterable<T>) () -> myDeque.descendingIterator()) {
  // use item
}

即。将Iterator包裹在Iterable lambda中。这几乎就是你使用匿名类所做的事情,但它对lambda来说更好一些。

当然,您总是可以使用Iterator.forEachRemaining()

myDeque.descendingIterator().forEachRemaining(t -> {
  // use item
});

答案 6 :(得分:1)

Guava当然有一个反向迭代场景的解决方案,但不幸的是你需要两个步骤。 Iterables.reverse()List为参数,而不是Iterable

final Iterable<String> it = Arrays.asList("a", "b", "c");
for(final String item : Iterables.reverse(Lists.newArrayList(it))){
    System.out.println(item);
}

输出:

  

Ç
  b
  一个

答案 7 :(得分:0)

我建议使用工厂方法创建一个helper类,你可以这样使用:

import static Iter.*;

for( Element i : iter(elements) ) {
}

for( Element i : iter(o, Element.class) ) {
}

下一步,iter()的返回类型可以是一个流畅的界面,所以你可以这样做:

for( Element i : iter(elements).reverse() ) {
}

或者

for( Element i : reverse(elements) ) {
}

您还应该看一下使用非常好的API解决许多这些问题的op4j

答案 8 :(得分:0)

Apache Commons Collections API具有一个名为IteratorIterable的类,可以完全做到这一点:

Iterator<X> iter;
for (X item : new IteratorIterable(iter)) {
    ...
}