当增强的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
?让我使用当前的设计可能会有很长的路要走。
我可能会做的只是创建一个名为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) { ... }
我想从各种答案中收集一些潜在的原因,为什么for-each循环不能简单地接受迭代器。
for ( Row r : table )
意味着极其可读,因为“对于表中的每一行”r“......”。看到for ( Row r : table.backwardsIterator() )
打破了这种可读性。Iterable
又是Iterator
,那么这种行为会是什么?虽然很容易制定一致的规则(例如Iterator之前的Iterable),但开发人员的行为将变得不那么透明。此外,必须在编译时检查这一点。Iterator
并将其范围限制为循环。这使得循环以两种方式“只读”:它不暴露迭代器,意味着没有(容易)有形的东西,它的状态被循环改变,也不能改变循环中操作数的状态(正如你可以通过remove()
直接与Iterator接口)。自己传递迭代器必然意味着迭代器被暴露,使你失去循环的那些“只读”属性。而不是创建一个descendingIterator
,最好编写一个descendingIterable()
方法来返回基于deque的降序迭代 - 它基本上取代了你的匿名类。这对我来说似乎很合理。根据Colin的建议,每次调用自己的descendingIterator
方法时,此方法返回的可迭代实现将在原始deque上调用iterator()
。
如果你只有一个迭代器并希望保持这种方式,你必须编写一个包含迭代器的Iterable<T>
实现并返回一次,如果多次调用iterator()
则抛出异常。这样可行,但显然很难看。
Guava用户可以使用ImmutableList.copyOf(Iterator)
将Iterator安全地转换为Iterable。尽管在Iterator上循环似乎很简单,但人们担心foreach会隐藏,而最安全的选择是创建一个像列表一样稳定的数据结构。
这也在Idea Graveyard中讨论过:
最大的担忧是
Iterable
通常被认为能够生成多个独立的迭代器。文档没有说明这一点,但是Collection
doc也没有这样说,但我们假设它的迭代器。当违反此假设时,我们在谷歌遭遇破产。最简单的解决方法是
ImmutableList.copyOf(Iterator)
,它非常快速,安全,并提供许多其他优点。
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循环实际上是为了使通用场景更短。并且调用另一个方法会使语法更加冗长。它可以支持Iterable
和Iterator
,但如果对象通过同时实现了呢? (会很奇怪,但仍有可能)。
Java 8中的惯用方法(是一种冗长的语言)是这样的:
for (T t : (Iterable<T>) () -> myDeque.descendingIterator()) {
// use item
}
即将Iterator
包裹在Iterable
lambda中。这几乎就是你使用匿名类所做的事情,但它对lambda来说更好一些。
当然,你总是可以使用Iterator.forEachRemaining()
:
myDeque.descendingIterator().forEachRemaining(t -> {
// use item
});
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 一个
我建议用工厂方法创建一个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解决了许多这些问题。
Apache Commons Collections API有一个名为IteratorIterable
的类就是这样做的:
Iterator<X> iter;
for (X item : new IteratorIterable(iter)) {
...
}