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

Mar*_*ers 32 java foreach guava

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

for ( T item : /*T[] or Iterable<? extends T>*/ ) {
    //use item
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

我失去了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
}
Run Code Online (Sandbox Code Playgroud)

哪个不值得使用.

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

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

Mar*_*ers 28

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

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

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

  • 令人困惑的是,for语句看似只是一个读操作,但会修改其操作数的状态.今天你可以通过一个字符串列表"foreach"一次找到最长的长度,然后再次执行以查找该长度的所有字符串.但是如果你改变那些代码来代替Iterator并且foreach仍然有效,那么你将主要模糊代码现在被严重破坏的事实. (4认同)
  • 实际上第3部分是恕我直言并不那么容易.因为它是编译时与运行时.在编译时看起来像迭代器的对象在运行时可能会变成Iterable,突然它会出现意外行为. (2认同)

Col*_*inD 16

我可能会做的只是创建一个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();
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

Deque<String> deque = ...
for (String s : deque::descendingIterator) { ... }
Run Code Online (Sandbox Code Playgroud)

  • Java 8方法很有效,不幸的是要求使用强制转换`(Iterable <String>)`来为方法引用提供目标类型. (3认同)

Jon*_*eet 9

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

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

  • 同意......你可以创建一个你自己的实用程序类,比如说,'Deques`并且有一个类似`<T> Iterable <T> asDescendingIterable(Deque <?extends T> deque)`的方法.这似乎是最合理的,因为你得到了一个'Iterable'的实际正确(可重用)实现.如果它能够支持Java 1.6,我觉得Guava可能会有这样的东西. (3认同)
  • @Mark:科林的建议是我的目标; 我显然没有表达清楚:) (3认同)
  • @Mark:`Iterable`应该每次都返回一个有效的,新的`Iterator` ...如果它不能这样做,它的`iterator()`方法不应该被允许被多次调用,因为它可以否则会给同一个"迭代器"提供多个引用.这可能导致某些东西期望它迭代"Iterable"所代表的所有元素,而实际上单个"Iterator"完全花费并且没有任何东西可读,或者更糟.一般来说,我认为最好不要尝试将单个"Iterator"表示为"Iterable". (2认同)

Luk*_*der 5

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

for (T t : (Iterable<T>) () -> myDeque.descendingIterator()) {
  // use item
}
Run Code Online (Sandbox Code Playgroud)

即包装IteratorIterablelambda 中。这几乎是您自己使用匿名类所做的,但使用 lambda 会更好一些。

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

myDeque.descendingIterator().forEachRemaining(t -> {
  // use item
});
Run Code Online (Sandbox Code Playgroud)