当增强的for循环(FracH循环)被添加到Java时,它被用来与数组或EDCOX1(0)的目标一起工作。
1 2 3
| for ( T item : /*T[] or Iterable<? extends T>*/ ) {
//use item
} |
这对于只实现一种迭代的集合类非常有用,因此具有单个iterator()方法。
但是,我发现自己非常沮丧,有时我想使用集合类中的非标准迭代器。例如,我最近试图帮助某人使用Deque作为后进先出/堆栈,但随后按FIFO顺序打印元素。我被迫这样做:
1 2 3 4
| for (Iterator<T> it = myDeque.descendingIterator(); it.hasNext(); ) {
T item = it.next();
//use item
} |
号
我失去了for each循环的优势。不仅仅是按键。如果不需要的话,我不喜欢公开迭代器,因为很容易犯两次调用it.next()等错误。
现在理想情况下,我认为for-each循环也应该接受一个Iterator。但事实并非如此。在这种情况下,是否有一种惯用的方法来使用for each循环?我也很乐意听到一些建议,比如使用像番石榴这样的公共图书馆。
在没有助手方法/类的情况下,我能想到的最好方法是:
1 2 3
| for ( T item : new Iterable<T>() { public Iterator<T> iterator() { return myDeque.descendingIterator(); } } ) {
//use item
} |
这不值得使用。
我很想看到番石榴有像Iterables.wrap这样的习惯用法,但没有找到这样的用法。显然,我可以通过类或助手方法来滚动我自己的迭代器包装器。还有其他想法吗?
编辑:作为旁注,有人能给出一个合理的理由来解释为什么增强的for循环不应该仅仅接受Iterator?让我活在当前的设计中可能会有很长的路要走。
- 注意:guava与任何将迭代器视为iterable的实用程序相反。返回的iterable将是一个等待发生的意外,循环遍历它不会真正缩短您的代码。
- @凯文:你能举出一个例子吗(功能请求评论或其他东西?)
- 噢,@kevin,我想你可以引证一下自己。配置文件读取。
- 作为记录,我在guava的"想法墓地"中发现了这个想法:code.google.com/p/guava-libraries/wiki/idea graveyard。
- 对于懒惰的用户,guava用户可以执行ImmutableList.copyOf(Iterator)来安全地将迭代器转换为iterable。有关原因的详细信息,请参阅上面的链接。
- @dimo414 imho你真的应该把你的评论转换成答案!我认为它足够简洁明了。
为什么增强的for循环不接受迭代器?
我想从各种答案中收集一些潜在的原因,来解释为什么for each循环不简单地接受迭代器。
便利性:for-each循环的创建部分是为了方便执行给定集合中每个元素的操作。它没有替换迭代器显式使用的义务或意图(显然,如果要删除元素,则需要显式引用迭代器)。
可读性:for each loop for ( Row r : table )的意思是"for each row"r"in table…"非常可读。看到for ( Row r : table.backwardsIterator() )会破坏可读性。
透明度:如果一个对象同时是一个Iterable和一个Iterator,那么它的行为是什么?虽然很容易制定一致的规则(例如,在迭代器之前是ITerable),但是对于开发人员来说,行为将变得不那么透明。此外,还必须在编译时检查这一点。
封装/范围:这是(在我看来)最重要的原因。for-each循环的设计目的是封装Iterator,并将其范围限制为循环。这使得循环以两种方式"只读":它不公开迭代器,这意味着没有任何(容易)有形的东西可以被循环改变其状态,也不能改变循环中操作数的状态(就像通过remove()直接与迭代器接口一样)。亲自传递迭代器必然意味着迭代器是公开的,这会使您丢失循环的两个"只读"属性。
- 它会好的,会出现confusing for the statement类读操作只会"嗯,但其操作数的调整。今天你可能在"foreach list of strings"一盎司to find the最长的长度,然后再做一次,在大学找到字符串的长度。但如果你改变代码的行为,而不是在线加工和安foreach迭代器仍然好,你majorly obscuring事实that the code is the terribly破了。
- 凯文:我想去",在与胶囊成囊和扫帚。这是不是真的在读操作中,You can obtain the迭代器仍然混乱和uses和EN EN as as You想要的多。the current foreach难做,只是让更多的环。
- "凯文是Iterables.reverse()version of about an过重,以Deque?
- 威利:"如果不正确colind' S,T attempt自制安石榴,盖在1.6×added喃喃。我期望会有一些好的东西to add。see also colind' S的回音。
- "你希望马克吧,让Google is moving to 1.6快。
- 它似乎likely to发生不快。
- is not that part 3看来真的很容易。因为它是编译时和运行时。安安在对象迭代器类,北转弯时可以编译在运行时iterable out to be an,en behaves unexpectedly突然。
- anony @:那在大点。我想我唯一的选择是依据EN to on the expression of the /静电型决策变量,since the needs to be后自制的磁带,在磁带(我真的没什么可区别于for - each from a for循环,uses安环explicitly迭代器;特征是在字节码的语言特征,not)。
- 我一直considering this,太。但即使当时它may be for irritating,泛型,when the type is at甚至somewhat动态编译的时间。
我可能会做的只是创建一个名为Deques的实用程序类,它可以支持这个,如果需要的话,还可以与其他实用程序一起使用。
1 2 3 4 5 6 7 8 9 10 11
| 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中,您将能够编写类似这样的方法,该方法引用EDCOX1的11条匹配EDCOX1(7)的签名:
1 2
| Deque <String > deque = ...
for (String s : deque ::descendingIterator ) { ... } |
- the Java工厂方法8,unfortunate with the is necessary that a铸造需求的目标提供(Iterable)method for the reference type。
与其创建一个descendingIterator,不如编写一个descendingIterable()方法,返回一个基于deque的降序iterable,它基本上取代了匿名类。我觉得这很合理。根据Colin的建议,此方法返回的可迭代实现在每次调用其自己的iterator()方法时,都会在原始deque上调用descendingIterator。
如果您只有一个迭代器,并且希望保持这种状态,那么您必须编写一个Iterable的实现,它包装迭代器并返回它一次,如果多次调用iterator(),则抛出异常。这是可行的,但显然是相当难看的。
- @琼斯基特:这是关于descendingIterable()的一个很好的观点,这就是你如何通过地图:你只需做for ( Entry e : myMap.entrySet() )就可以了。不幸的是,Deque是一个标准的库类,而不是我的类。
- 您能否扩展为什么需要这样的条件:如果多次调用iterator(),它应该抛出一个错误?我假设您这样说,这样如果您泄漏对iterable的引用,就不会提供过时的迭代器?
- 同意。。。您可以创建自己的实用程序类,比如说,Deques,并使用类似 Iterable asDescendingIterable(Deque extends T> deque)的方法。这似乎是最合理的,因为您得到了Iterable的实际正确(可重用)实现。我觉得番石榴很可能有这样的东西,如果它能够支持Java 1.6。
- @马克:一个Iterable每次应该返回一个有效的、新鲜的Iterator…如果不能这样做,则不应允许多次调用它的iterator()方法,因为它可能在其他情况下对同一Iterator给出多个引用。这可能导致一些预期,它正在迭代Iterable表示的所有元素,而实际上,单个Iterator是完全耗尽的,没有任何可读取的内容,或者更糟。总的来说,我认为最好不要把一个Iterator表示为一个Iterable。
- @科林德:你说得对,这是另一个重要的问题。不管怎样,它都是关于防止引用ITerable泄漏的影响。伙计,对我来说越来越明显的是他们应该设计该死的循环来接受迭代器。
- +我想说一下为什么包装纸没有看上去那么好。
- @科林德:我认为你的第一条评论是目前为止最好的答案(它不同于乔恩的建议)。考虑将其添加为答案。
- @马克:科林的建议正是我的目标,我显然不够清楚地表达出来。)
- @马克·彼得斯:我已经把它作为一个答案加上去了,尽管它和博佐的答案非常相似……只是一点更好的语法,不公开实现类。
guava用户可以执行ImmutableList.copyOf(Iterator)来安全地将迭代器转换为iterable。尽管在迭代器上循环看起来很简单,但有人担心foreach会隐藏起来,最安全的选择是创建一个稳定的数据结构,如列表。
这一点也在思想墓地中讨论:
The biggest concern is that Iterable is generally assumed to be able to produce multiple independent iterators. The doc doesn't say this, but the Collection doc doesn't say this, either, and yet we assume it of its iterators. We have had breakages in Google when this assumption was violated.
The simplest workaround is ImmutableList.copyOf(Iterator), which is pretty fast, safe, and provides many other advantages besides.
号
Java 8(作为一种冗长的语言)的习惯用法是:
1 2 3
| for (T t : (Iterable<T>) () -> myDeque.descendingIterator()) {
// use item
} |
也就是说,将Iterator包在Iterablelambda中。这基本上就是你自己使用匿名类所做的,但是lambda更好一些。
当然,你可以一直使用Iterator.forEachRemaining():
1 2 3
| myDeque.descendingIterator().forEachRemaining(t -> {
// use item
}); |
号
1 2 3 4 5 6 7 8 9 10 11
| 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();
}
} |
号
然后
1 2 3
| for (T item : new DescendingIterableDequeAdapter(deque)) {
} |
因此,对于每种情况,您都需要一个特殊的适配器。我认为理论上不可能做你想做的事情,因为这个工具必须知道迭代器返回的方法存在,这样它才能调用它们。
至于你的另外一个问题,我认为是因为for-each循环实际上是为了缩短通用场景的时间。调用一个额外的方法会使语法更加冗长。它本可以同时支持Iterable和Iterator,但如果通过的对象同时实现了这两个功能,会怎么样?(很奇怪,但还是有可能的)。
- 当然可以,但如果必须为每种类型的迭代器编写一个新的类,就不太习惯了。我希望在给定一个任意的迭代器的情况下能够工作。
- @马克·彼得斯-不可能有这样的事。充其量,它会将所有这些具体的适配器隐藏在静态实用工具方法后面,该方法至少使用一些反射,或者具有一个枚举来选择所需的类型。
- @博佐:不可能有这样的东西来返回一个有效的、可重用的ITerable,这是真的。但我只想将迭代器推入for循环,有效的iterable或无效的iterable。
- @马克·彼得斯:这很简单,所以我建议你自己做一个方法。我不认为任何一个图书馆,至少不是像番石榴这样的高质量图书馆,会有这样的功能,鼓励人们做潜在的坏事。
- @博佐或任何人:我没有密切关注整个关闭问题。如果函数指针指向任何返回迭代器的no arg函数,就可以很容易地做到这一点。任何人都知道,如果项目lambda,假设它进入Java 7,会支持这样的事情吗?
- @马克·彼得斯:事实上,我在回答中提到了这一点……这应该很容易!然而,lambda项目在2012年正式下滑至JDK 8。
- 感谢@bozho回答了这个问题。希望我能再给你一个+1。我把它变成了一个答案,试图收集一些潜在的原因。
当然,Guava有一个针对逆向不可迭代场景的解决方案,但不幸的是,您需要两个步骤。reverse()将List作为参数,而不是Iterable。
1 2 3 4
| final Iterable <String > it = Arrays. asList("a", "b", "c");
for(final String item : Iterables. reverse(Lists. newArrayList(it ))){
System. out. println(item );
} |
输出:
c
b
a
号
- 以descendingIterator为例。也许您有一个树集合,其中iterator()执行深度优先搜索,breadthFirstIterator()执行BFS。
- 可能有一个重载版本的Iterables.reverse(),它使用Deque,而不是List。会是个不错的补充。
- 番石榴只支持Java 1.5,但Deque是1.6加法。
ApacheCommonsCollectionsAPI有一个名为IteratorIterable的类,用于执行以下操作:
1 2 3 4
| Iterator<X> iter;
for (X item : new IteratorIterable(iter)) {
...
} |
我建议用工厂方法创建一个助手类,您可以这样使用:
1 2 3 4 5 6 7
| import static Iter. *;
for( Element i : iter (elements ) ) {
}
for( Element i : iter (o, Element. class) ) {
} |
。
作为下一步,iter()的返回类型可以是一个流畅的接口,因此您可以执行以下操作:
1 2
| for( Element i : iter (elements ). reverse() ) {
} |
。
或者可能
1 2
| for( Element i : reverse (elements ) ) {
} |
您还应该了解一下op4j,它用一个非常好的API解决了许多这些问题。
- 你会如何Iterable反向安Iterator或任意?我甚至不喜欢我有一个周末,你可以从启动。
- "我想:为什么所有的colind安任意反向迭代器?在我大学的100%完成这个迭代器的代码转换和95% of them quickly。that for which叶5%需要特别的房子,但不useless make the code for the other 95%(when some like an Enumeration归来老班或List没有GENERIC型)。
- 你的方法的能力to the蓝晶石反向迭代器的任何课程,虽然你可能会认为of document that should be used only with IT负载的迭代器。但我真的做,你have to copy the contents of the first迭代器,迭代,然后通过在反向复制,似乎wasteful which。
- 默认@ colind:the Java API只是不提供足够的信息反向迭代器which to create a总是工作。我知道我最好is the next thing。如果你不喜欢它,而不是使用ListIterator。