当增强的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
?这可能会让我对目前的设计感兴趣。
答案 0 :(得分:26)
我想从各种答案中收集一些可能的原因,为什么for-each循环不会简单地接受迭代器。
for ( Row r : table )
意味着极为可读,因为“对于表格中的每一行”r“...”。看到for ( Row r : table.backwardsIterator() )
打破了这种可读性。Iterable
又又是Iterator
,那么该行为会是什么?虽然很容易制定一致的规则(例如Iterator之前的Iterable),但开发人员的行为将变得不那么透明。此外,必须在编译时检查这一点。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会隐藏,而最安全的选择是创建一个像列表一样稳定的数据结构。
最大的担忧是
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循环实际上是为了使通用场景更短。并且调用另一个方法会使语法更加冗长。它可以同时支持Iterable
和Iterator
,但如果传递的对象同时实现了呢? (会很奇怪,但仍有可能)。
答案 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)) {
...
}