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

问题描述 投票:29回答:9

当增强的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
}

哪个不值得使用。

我很高兴看到Guava有像Iterables.wrap这样的东西来制作这个惯用语,但没有找到类似的东西。显然,我可以通过类或辅助方法滚动我自己的Iterator包装器。还有其他想法吗?

编辑:作为旁注,任何人都可以给出一个有效的理由,说明为什么增强的for循环不应该只接受Iterator?让我使用当前的设计可能会有很长的路要走。

java guava foreach
9个回答
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();
      }
    }
  }
}

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

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

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()直接与Iterator接口)。自己传递迭代器必然意味着迭代器被暴露,使你失去循环的那些“只读”属性。

9
投票

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

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


4
投票

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

这也在Idea Graveyard中讨论过:

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

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


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,但如果对象通过同时实现了呢? (会很奇怪,但仍有可能)。


3
投票

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

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

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

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

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

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

输出:

C b 一个


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

您还应该看看op4j,它通过一个非常好的API解决了许多这些问题。


0
投票

Apache Commons Collections API有一个名为IteratorIterable的类就是这样做的:

Iterator<X> iter;
for (X item : new IteratorIterable(iter)) {
    ...
}
© www.soinside.com 2019 - 2024. All rights reserved.