是一个“无限”的迭代器坏devise?
提供“无限”的Iterator
实现通常被认为是不好的做法。 即哪里调用hasNext()
总是(*)返回true?
通常我会说“是”,因为调用代码可能会有不正常的行为,但在下面的实现中,除非调用者从列表中移除迭代器初始化的所有元素,否则hasNext()
将返回true。 即有终止条件 。 你认为这是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
如何被用来从无限序列(如斐波那契数列)生成值。 但是,Java Iterator
文档指出,迭代器是:
一个集合的迭代器。
现在你可以争辩说Fibonacci序列是一个无限的集合,但是在Java中,我将集合与java.util.Collection
接口等同起来,它提供了诸如size()
这样的方法,意味着集合必须是有界的。 因此,使用Iterator
作为来自无界序列的值的生成Iterator
是合法的吗?
我认为这完全是合法的 – Iterator
只是一个“东西”的stream。 为什么这个stream必然是有界的?
其他许多语言(如Scala)都有无限stream的概念,这些stream可以被迭代。 例如,使用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();
Iterator
的全部意义在于它是懒惰的,也就是说它只能提供尽可能多的对象。 如果用户要求无限Iterator
所有对象,这是他们的问题,而不是你自己的问题。
虽然我也认为这是合法的,但是我想补充一下,这样一个Iterator
(或者更准确地说:一个可Iterator
生成这样一个Iterator
)在增强型for循环(aka for for each-loop)中将不能很好地发挥作用:
for (Object o : myIterable) { ... }
由于增强for循环中的代码不能直接访问迭代器,因此无法在迭代器上调用remove()
。
所以要结束循环,必须执行以下操作之一:
- 访问
Iterator
的内部List
并直接删除对象,可能会引发ConcurrentModificationException
- 使用
break
来退出循环 - “使用”
Exception
退出循环 - 使用
return
来离开循环
除了最后这些替代方法之外的所有方法都不是留下增强的for-loop的最好方法。
当然,“正常的”for循环(或任何其他循环以及显式的Iterator
variables)都可以正常工作:
for (Iterator it = getIterator(); it.hasNext();) { Object o = it.next() ... }
这可能是语义上的,但迭代器应该认真地返回下一个项目,而不考虑它何时结束。 结尾是一个集合的副作用,虽然它可能看起来像一个常见的副作用。
但是,无限的,10万亿的collections品有什么区别呢? 来电者要么全都要,要么全都不要。 让调用者决定要返回多less项目或何时结束。
我不会说调用者不能使用for-each构造。 他可以,只要他想要所有的物品。
在集合的文档中的东西,如“可能是一个无限的集合”将是适当的,但不是“无限迭代器”。
这是一个完全合法的使用 – 只要它被正确logging 。
使用名称CyclicIterator
是一个好主意,因为它推断如果循环出口的情况没有被正确定义的话,循环迭代器可能是无限的。
当您创build无限数据时,无限迭代器非常有用,例如斐波那契数列等线性循环序列 。
所以这样使用完全没问题。
是。 什么时候停止迭代只需要一个不同的标准。
无限列表的概念在像Haskell这样的懒惰评估语言中很常见,并导致完全不同的程序devise。
实际上,无论JavaDoc API如何, Iterator
都来自Iterable
而不是 Collection
。 后者扩展了前者,这就是为什么它也可以生成一个Iterator
,但是对于一个Iterable
,这是完全合理的,而不是一个Collection
。 实际上,甚至有一些Java类实现了Iterable
而不是Collection
。
现在,至于期望……当然,如果没有明确的指示,就不能提供无限的迭代器。 但有时候,期望可能是骗人的。 例如,可能会期望一个InputStream
总是结束,但这并不是它的要求。 同样,从这样的stream中读取行的Iterator
可能永远不会结束。
考虑你是否有一个发电机function。 迭代器可能是一个发生器的合理接口,或者你可能已经注意到了。
另一方面,Python有发生器终止。
我可以想象任意数量的用于无限迭代器的用法。 就像一个程序迭代通过另一个服务器或后台进程发送的状态消息一样。 在现实生活中,服务器最终会停下来,从程序的angular度来看,它可能只是继续阅读,直到它被迭代器无关的东西停止。
这肯定是不寻常的,正如其他人所说,应该仔细logging。 但我不会宣布这是不可接受的。
如果你有一个对象需要一个迭代器,而你恰好给它一个无限的迭代器,那么一切都应该是好的。 当然,该对象不应该要求迭代器停止。 那是潜在的不好的devise。
想想音乐播放器。 您告诉它在“连续播放模式”下开始播放曲目,并播放曲目,直到您告诉它停止。 也许你通过将一个播放列表永久地拖曳到这个连续播放模式中,直到用户按下“停止”。 在这种情况下, MusicPlayer
需要通过播放列表进行迭代, 永远可以是Collection<Track>
。 在这种情况下,你需要一个CyclicIterator<Track>
或者一个ShuffleIterator<Track>
来随机select下一个音轨,永远不会用完。 到现在为止还挺好。
MusicPlayer.play(Iterator<Track> playlist)
不关心播放列表是否结束。 它可以通过一个迭代器遍历一个轨道列表(播放到最后,然后停止),并在一个ShuffleIterator<Track>
列表上使用一个ShuffleIterator<Track>
(永远select一个随机轨道)。 一切还好。
如果有人试图编写MusicPlayer.playContinuously()
希望永远播放曲目(直到有人按下“停止”),但接受一个Iterator<Track>
作为参数? 这将是devise问题。 显然, playContinuously()
需要一个无限的Iterator
,如果它接受一个普通的Iterator
作为参数,那么这将是不好的devise 。 例如,它将需要检查hasNext()
并处理返回false
的情况,即使它永远不会发生。 坏。
所以实现所有你想要的无限迭代器,只要它们的客户端不依赖那些迭代器结束。 就好像你的客户端依赖迭代器永远持续下去,那么不要给它一个简单的Iterator
。
这应该是显而易见的,但似乎不是。