提供"无限"的Iterator实现通常被认为是不好的做法;即,对hasNext()的调用总是返回true?
通常我会说"是",因为调用代码的行为可能不稳定,但在下面的实现中,除非调用方从迭代器初始化的列表中删除所有元素,否则hasNext()将返回true;即,存在终止条件。你认为这是对Iterator的合法使用吗?这似乎并没有违反合同,尽管我想有人会说这是不具有约束力的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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从无界序列(如斐波那契序列)生成值。然而,JavaEDCOX1 0文档说明迭代器是:
An iterator over a collection.
现在你可以说Fibonacci序列是一个无限的集合,但是在Java中,我将集合等同于EDCOX1的6接口,它提供了诸如EDCX1(7)的方法,这意味着集合必须是有界的。因此,使用Iterator作为无界序列值的生成器是否合法?
- 我不熟悉Java,但这真的有用吗?如果您在遍历列表时从列表中删除了一些内容,它不会引发异常吗?
- 好问题。期待一些有趣的答案。
- 迭代器实现通常设计为快速失败,因为直接从基础集合中移除项将导致迭代器抛出ConcurrentModificationException。但是,通过迭代器的remove()方法删除项是一种安全的方法。
- google collections/guava在迭代器类中有一个静态方法,用于创建迭代器,该迭代器在底层迭代器中无限循环所有元素。
- 从Java 6 EDCOX1的0个方面JavaDoc:"如果这个集合包含多于EDCOX1,1个元素,则返回EDCOX1 OR 1"。因此,它不会违反EDCOX1×3的契约,以创建一个EDOCX1 4实现EDCOX1,5,返回EDCOX1×6。但是,您不能通过get方法访问超过Integer.MAX_SIZEth元素的内容,因此我怀疑这样的集合是否具有实际的用途!
- ISME:可以使用迭代器访问无限集合!
- JSB?????只有通过迭代器的remove()方法以外的其他方法从集合中移除元素时,才会引发异常。
我认为这是完全合法的-一个Iterator只是一个"东西流"。为什么流必须是有界的?
许多其他语言(例如scala)都有内置无边界流的概念,并且可以迭代这些流。例如,使用scalaz
1 2 3 4 5
| 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 |
编辑:就最小惊喜原则而言,我认为这完全取决于上下文。例如,我希望这个方法返回什么?
1
| public Iterator<Integer> fibonacciSequence(); |
- +一个很好的观点。它改变了我的想法。
- 这样的概念绝对是合法的。一个重要的问题仍然是,如果这样的EDCOX1×0 }将违反Java中最不意外的原则。
- @约阿希姆-我认为这完全取决于上下文。参阅编辑
- @Oxbow:当然,如果是那样的话,我就没问题了。如果一个Iterable返回这样一个Iterator(见我的答案),可能会使事情变得混乱。有一种不言而喻的假设,即一个Iterable基本上表现为一个有限的Collection的行为,即返回有限数量的对象。
- 原则不是教条。你可以随意地提出一个例外,但要明确地说,这是一个例外。比如,在函数描述的顶部用粗体记录这个行为,并让这个名字自我解释。
- + 1。我用C中的yield关键字做了同样的事情。
一个Iterator的关键是它是懒惰的,也就是说它只给你想要的东西。如果用户要求一个无限的Iterator的所有对象,那是他们的问题,而不是你的问题。
这可能是语义,但是迭代器应该努力返回下一个项目,而不考虑它何时结束。结尾是一个集合的副作用,尽管它看起来像一个常见的副作用。
不过,无穷大和拥有10万亿个项目的集合有什么区别?调用者要么想要全部,要么不想要。让调用者决定返回多少项或何时结束。
我不会说调用者不能使用for-each构造。他可以,只要他想要所有的东西。
在集合的文档中,"可能是无限集合"之类的内容是合适的,但不是"无限迭代器"。
- 对!想想一个Web服务器:它只是一个针对每个循环的无限HTTP请求流。为什么要终止?事实上,如果一个Web服务器确实终止了,我们通常称之为"崩溃",并对此感到非常不安。您希望Web服务器是一个无限循环!
- 我和你在一起。如果某个返回ITerable,则调用方必须假定它不会终止。他们应该只假设他们可以对每一个项目进行操作。
虽然我也认为这是合理的,但我想补充一点,这样一个Iterator(或者更准确地说:一个Iterable产生这样一个Iterator)将不能很好地利用增强的for循环(每个循环都是一个k.a):
1 2 3
| for (Object o : myIterable ) {
...
} |
由于增强的for循环中的代码不能直接访问迭代器,因此它不能在迭代器上调用remove()。
因此,要结束循环,必须执行以下操作之一:
- 进入Iterator的内部List并直接移除物体,可能引发ConcurrentModificationException。
- 用break退出回路
- "使用"Exception退出回路
- 用return离开回路
除了最后一个选项之外,其他所有选项都不是留下增强for循环的最佳方法。
"正常"for循环(或任何其他循环以及显式的Iterator变量)工作正常,当然:
1 2 3 4
| for (Iterator it = getIterator (); it. hasNext();) {
Object o = it. next()
...
} |
- …或者使用"返回"退出循环,如果循环的目的是找到特定的元素,这实际上可能是一个很好的退出方法。
- 我不同意。至少break和return是完全可以接受的离开循环的方法。
- @康拉德:它们是可以接受的,但是增强的for循环强烈地意味着我要迭代Iterator提供的每个元素。同样,几乎没有人会期望在这样一个循环中出现一个break或return:for (int i=0; i<10; i++)。
- 使用break或return。如果您有一个ITerable,那么您必须假设它是一个无止境的TS流,除非您有其他的保证它不是。
对。对于何时停止迭代,您只需要一个不同的条件。
无限列表的概念在像Haskell这样的懒惰的评估语言中非常常见,并导致完全不同的程序设计。
无限迭代器在创建无限数据时非常有用,例如线性递归序列(如斐波那契序列)。
所以使用这样的工具是完全可以的。
- 我一直在考虑这个问题,不确定它是否违背了迭代器是"集合上的迭代器"的定义。查看我最近的编辑。
它是一种完全合法的使用——只要它被适当地记录下来。
使用名称CyclicIterator是一个好主意,因为它推断如果循环出口大小写没有正确定义,迭代器上的循环很可能是无限的。
- 考虑到这一点,您会说在使用循环器的情况下,API应该返回循环器,而不是迭代器?它使它非常明确,但不可能透明地更改实现,并且实际上在其实现中不提供任何其他方法。
- 考虑到适当的用例,我会考虑这一点——当然。
- 循环运算符并不意味着迭代器运行…好吧,在循环中?无穷大有什么问题?
- 好吧,那是个愚蠢的评论…拍自己的头。下次我会在写废话之前先读这个问题。
实际上,不管javadoc api怎么说,Iterator来自Iterable,而不是Collection。后者扩展了前者,这就是为什么它也可以生成一个Iterator,但对于Iterable而不是Collection是完全合理的。事实上,甚至有一些Java类实现EDOCX1,1,而不是EDOCX1,2。
现在,至于期望……当然,如果没有一个明确的指示,无限迭代器就不能被使用。但是,有时候,期望可能是骗人的。例如,人们可能期望一个InputStream总是结束,但这并不是一个要求。同样,从这样的流中读取的Iterator返回行可能永远不会结束。
考虑您是否具有生成器函数。迭代器可能是到生成器的合理接口,也可能不是,正如您注意到的那样。
另一方面,python有可以终止的生成器。
如果您有一个需要迭代器的对象,并且恰好给它一个无限迭代器,那么所有的都应该是好的。当然,该对象不应该要求迭代器停止。这就是潜在的坏设计。
想想音乐播放器。你告诉它在"连续播放模式"下开始播放曲目,它播放曲目,直到你告诉它停止。也许你可以通过永远改变播放列表来实现这种连续播放模式…直到用户按下"停止"。在这种情况下,MusicPlayer需要迭代播放列表,可能永远是Collection。在这种情况下,你要么想要一个你已经建造的CyclicIterator,要么想要一个随机选择下一个轨道并且永不耗尽的ShuffleIterator。到目前为止,一切都很好。
MusicPlayer.play(Iterator不在乎播放列表是否结束。它既可以使用迭代器遍历曲目列表(播放到结尾,然后停止),也可以使用ShuffleIterator遍历曲目列表(永远选择随机曲目)。一切还好。
如果有人试图写MusicPlayer.playContinuously(),希望"永远"播放曲目(直到有人按下"停止"),但接受Iterator作为参数,该怎么办?这就是设计问题。显然,playContinuously()需要一个无限的Iterator,如果它接受一个普通的Iterator作为参数,那将是一个糟糕的设计。例如,它需要检查hasNext(),并处理返回false的情况,即使这种情况永远不会发生。坏的。
因此,只要客户机不依赖于这些迭代器的结尾,就实现您想要的所有无限迭代器。就像您的客户机永远依赖于迭代器一样,那么不要给它一个普通的Iterator。
这应该是显而易见的,但似乎不是。
我可以想象无限迭代器的任何用途。比如,一个程序迭代另一个服务器或后台进程发送的状态消息。虽然在现实生活中,服务器最终可能会停止运行,但从程序的角度来看,它可能会一直读下去,直到被与迭代器无关的东西停止为止。
这当然是不寻常的,正如其他人所说,应该仔细记录。但我不会说这是不可接受的。
- 额外的想法:虽然我已经表示同意那些认为应该仔细记录这样一件事的人,但我突然想到,这可能比我最初认为的问题要小一些:如果有人在使用迭代器或任何其他构造,而不知道它应该返回什么,那么不管怎样,他都会遇到麻烦。什么!