通常认为提供“无限”的Iterator
实施是不好的做法;即,hasNext()
的调用总是(*)返回true?
通常我会说“是”,因为调用代码的行为可能不正常,但在下面的实现中hasNext()
将返回true,除非调用者从List中删除迭代器初始化的所有元素;即有终止条件。你认为这是Iterator
的合法使用吗?它似乎没有违反合同,虽然我认为有人可能认为它不直观。
public class CyclicIterator<T> implements Iterator<T> {
private final List<T> l;
private Iterator<T> it;
public CyclicIterator<T>(List<T> l) {
this.l = l;
this.it = l.iterator();
}
public boolean hasNext() {
return !l.isEmpty();
}
public T next() {
T ret;
if (!hasNext()) {
throw new NoSuchElementException();
} else if (it.hasNext()) {
ret = it.next();
} else {
it = l.iterator();
ret = it.next();
}
return ret;
}
public void remove() {
it.remove();
}
}
(迂腐)编辑
有些人评论了Iterator
如何用于从无限序列(如Fibonacci序列)生成值。但是,Java Iterator
文档声明Iterator是:
集合上的迭代器。
现在你可以说Fibonacci序列是一个无限集合但是在Java中我将等同于java.util.Collection
接口的集合,它提供了size()
等方法,暗示集合必须是有界的。因此,使用Iterator
作为无界序列的值生成器是否合法?
答案 0 :(得分:76)
我认为完全合法 - Iterator
只是一个“东西”流。为什么流必然是有限的?
许多其他语言(例如Scala)具有内置于其中的无界流的概念,这些可以迭代。例如,使用scalaz
scala> val fibs = (0, 1).iterate[Stream](t2 => t2._2 -> (t2._1 + t2._2)).map(_._1).iterator
fibs: Iterator[Int] = non-empty iterator
scala> fibs.take(10).mkString(", ") //first 10 fibonnacci numbers
res0: String = 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
编辑: 就最少惊喜的原则而言,我认为这完全取决于具体情况。例如,我希望这个方法返回什么?
public Iterator<Integer> fibonacciSequence();
答案 1 :(得分:19)
Iterator
的整点是它是懒惰的,即它只为你提供了所需数量的对象。如果用户要求无限Iterator
的所有对象,那就是他们的问题,而不是你的问题。
答案 2 :(得分:7)
虽然我也认为这是合法的,但我想补充一点,Iterator
(或者更确切地说:Iterable
产生这样的Iterator
)不会很好增强的for循环(又名for-each-loop):
for (Object o : myIterable) {
...
}
由于增强型for循环中的代码无法直接访问迭代器,因此无法在迭代器上调用remove()
。
因此,要结束循环,必须执行以下操作之一:
List
的内部Iterator
并直接删除对象,可能会引发ConcurrentModificationException
break
退出循环Exception
退出循环return
离开循环除了最后一个替代方案之外的所有方案都不是保留增强的for循环的最佳方式。
“正常”for循环(或任何其他循环以及显式Iterator
变量)当然可以正常工作:
for (Iterator it = getIterator(); it.hasNext();) {
Object o = it.next()
...
}
答案 3 :(得分:7)
这可能是语义,但迭代器应该努力返回下一个项目,而不考虑它何时结束。结束是一个集合的副作用,虽然它可能看起来像一个常见的副作用。
但是,无限和10万亿项目的收藏之间有什么区别?呼叫者要么全部都想要,要么他不想要。让来电者决定返回多少项或何时结束。
我不会说调用者不能使用for-each结构。只要他想要所有物品,他就可以。
集合的文档中的某些内容(如“可能是无限集合”)是合适的,但不是“无限迭代器”。
答案 4 :(得分:4)
这是完全合法的用途 - 只要有正确记录。
使用名称CyclicIterator
是一个好主意,因为它推断如果没有正确定义循环退出情况,迭代器上的循环很可能是无限的。
答案 5 :(得分:4)
无限的迭代器在创建无限数据时非常有用,例如:线性递归序列,如Fibonacci sequence。
所以使用它是完全没问题的。
答案 6 :(得分:4)
是。您只需要一个不同的标准来停止迭代。
无限列表的概念在像Haskell这样的惰性评估语言中很常见,并导致完全不同的程序设计。
答案 7 :(得分:3)
实际上,Iterator
来自Iterable
,不是 a Collection
,无论JavaDoc API如何说明。后者扩展了前者,这就是为什么它也可以生成Iterator
,但是Iterable
不能成为Collection
是完全合理的。实际上,甚至有一些Java类实现Iterable
但不实现Collection
。
现在,至于期望......当然,如果没有明确表明情况确实如此,则无法提供无限迭代器。但是,有时,期望可能是骗人的。例如,人们可能期望InputStream
总是会结束,但这确实不是它的要求。同样,从这样的流中读取的Iterator
返回行可能永远不会结束。
答案 8 :(得分:2)
考虑一下你是否有生成器功能。迭代器可能是生成器的合理接口,或者它可能不是,正如您所注意到的那样。
另一方面,Python有做终止的生成器。
答案 9 :(得分:1)
我可以想象无限迭代器的任意数量的用途。比如,一个程序迭代通过另一个服务器或后台进程发送的状态消息怎么样。虽然在现实生活中,服务器最终可能会停止,从程序的角度来看,它可能只是继续读取,直到它被与迭代器无关的东西停止到达终点。
这肯定是不寻常的,正如其他人所说,应该仔细记录。但我不会宣布这是不可接受的。
答案 10 :(得分:1)
如果你有一个需要迭代器的对象,并且碰巧给它一个无限的迭代器,那么一切都应该是好的。当然,该对象永远不应该要求迭代器停止。 是潜在的糟糕设计。
想想一个音乐播放器。你告诉它开始播放曲目&#34;连续播放模式&#34;它会播放曲目,直到你告诉它停止。也许你通过永久地改变播放列表来实现这种连续播放模式......直到用户按下&#34;停止&#34;。在这种情况下,MusicPlayer
需要对播放列表进行迭代,可能是Collection<Track>
,永远。在这种情况下,您需要构建CyclicIterator<Track>
,或者ShuffleIterator<Track>
随机选择下一首曲目并且永远不会用完。到目前为止,非常好。
MusicPlayer.play(Iterator<Track> playlist)
并不关心播放列表是否结束。它既适用于轨道列表上的迭代器(播放到结尾,然后停止),也适用于轨道列表上的ShuffleIterator<Track>
(永远选择随机轨道)。一切都还不错。
如果有人试图写MusicPlayer.playContinuously()
- 期望播放曲目&#34;永远&#34; (直到有人按下&#34;停止&#34;),但接受Iterator<Track>
作为参数? 那将是设计问题。显然,playContinuously()
需要无限Iterator
,如果它接受普通Iterator
作为参数,那么将是糟糕的设计。例如,它需要检查hasNext()
并处理返回false
的情况,即使它永远不会发生。坏。
所以实现你想要的所有无限迭代器,只要他们的客户不要依赖那些迭代器结束。好像你的客户依赖迭代器永远进行,然后不要给它一个简单的Iterator
。
这应该是显而易见的,但似乎并非如此。