迭代器可以在迭代期间删除元素吗,迭代器的删除方法
Java迭代器退出迭代
随着这学期的结束,我想我会分享一个关于如何非常熟悉Java迭代器的小故事。
就语境而言,我已经开设了第二年的软件构件课程,这是学生试图进入这个专业的最后一道障碍。当然这门课对学生来说压力很大,我常常要加倍努力,为他们提供一切成功的机会。
不幸的是,这学期我们被疫情包围了,不得不转向在线教学。因此,我们不得不在教学方面迅速做出一些决定,从而改变学生的学习方式。特别是,我们将所有的纸笔测试转换为在线测试。
对于一些学生来说,这是一大幸事。毕竟这些考试并不比考试难,所以我们设置为公考。换句话说,我们让他们更容易通过课程。
当然,学生遍布全球,他们得不到需要的帮助。而且学生学习也没有考试努力。这种组合造成了一些非常糟糕的考试成绩。
到我们第四次考试的时候,学生们已经很沮丧了。事实上,我听几个老师说,他们的学生厌倦了“技术问题”。作为一名讲师,听到这些有点沮丧,因为它们是非常典型的考试问题。我们没有给他们增加困难,但这是我第一次听到这些抱怨。
然后,奇怪的事情发生了。我们给了他们一个我真的不知道答案的问题,结果和下面这个有点像:
以下代码片段后的Set NaturalNumber nums变量的值是什么?
set natural number nums=new someset implementation();nums . add(new natural number 2(1));nums.add(新自然数字2(5));nums.add(新自然数字2(6));for(natural number n:nums){ n . increment();}当然,学生的选择如下:
Nums={1,5,6,2,6,7} nums={2,6,7} nums={1,5,6}无法根据提供的信息进行区分。现在,就上下文而言,这个例子中有一些内部组件。
首先,NaturalNumber是一个可变类,表示无界非负整数。换句话说,自然数的范围可以从零到无穷大。此外,可以使用一系列基本数学运算来修改NaturalNumber,如下所示:
Increase()加1 this add(自然数n):在this上加n。另外,这个问题中用到的集合类似于一个数学集合。这里的想法是集合有两个主要属性:
集合缺少重复项(即{1,2,1}不是合法的集合)。集合是无序的(即{1,2,3}和{3,2,1}是等价的)。作为参考,如果你有兴趣阅读更多细节,请在课程网站上完整记录这两个组成部分。所有组件都是使用“契约式设计”编写的,因此每个方法都将包含一个适当的契约,其中前置条件由@ requirements表示,后置条件由@ guarantees表示。
此外,我们使用@ restores、@replaces、@ clearing和@ replaces等参数模式来标记每个参数。当然,这超出了本文的范围。
现在解决问题,我重申一下,我一开始也不确定确切答案是否正确。显然,第一个答案(即{1,5,6,2,6,7})是错误的,因为增加基值不会给集合增加新值——或者我认为。使用相同的逻辑,我还假设第三组(即{1,5,6})显然是不正确的,因为我们显然是在改变基值。
此时,我相当有信心第二个答案(即{2,6,7})是正确的,因为我的学生中有87%是正确的。当然,我有答案钥匙,所以我必须挑战自己,去理解为什么正确答案实际上是最终答案(即“从提供的信息中无法判断。”)。
现在根据这篇文章的标题,你可能已经走在我前面了。没关系!但是,我并没有马上得出这个结论。相反,我后退了一步,决定真的画一套。
当然,你在尝试这样做的时候会遇到几大问题。首先,我之前提到过,Set是没有顺序的。这样一来,如何才能推断出迭代过程中哪个元素先出现?我们会尝试所有可能的配置吗?
这些是我还没准备好处理的问题。幸运的是,事实证明按照出现的顺序迭代可以节省很多时间。看一看:
{1,5,6}//initialstate {2,5,6 }//增加第一个元素后{2,6,6 }//增加第二个元素后!我们打破了第一条规则:集合不能包含重复项。因此,我们无法确定结果集将会是什么样子。我最后的答案是D:“从提供的信息看不出来。”
可惜这个解释我不满意。比如,我知道集合不能包含重复项,但是违反这个规则的实际后果是什么?换句话说,如果情况如此糟糕,我们为什么要让用户访问基础数据?
在我看来,用户应该只在删除数据后访问数据。总的来说,我认为图书馆在这方面做得不错。如果Set没有实现Iterable,那么我们就迭代。
Java迭代器介绍这给我带来了一个比较奇怪的问题:Java迭代器。为了让这段代码工作,Set必须实现Iterable,这意味着为底层架构定义一个迭代器。
现在,如果您曾经编写过自己的迭代器,那么您需要做以下事情:
new iterator t(){ @ override public boolean has next(){.} @覆盖public t next () {.} @覆盖公共void remove () {.}}这里的基本思想是,我们定义某种可以充当惰性数据结构的结构。如果您熟悉其他语言(例如Python)中的生成器表达式,您会有同样的想法:我们创建了一个对象,它可以从一系列项目中一次返回一个项目。
实际上,迭代器的工作方式是通过next()方法继续提供条目,直到没有返回值(这可能永远不会发生)。在有界序列中,我们知道何时停止,因为hasNext()方法将返回false。总之,这些方法可以作为循环机制的核心:
while(ITER . has next()){ T item=next();}通过使一个类实现Iterable,我们可以利用一些Java语法糖,称为for-each循环:
对于(测试项目:集合){.} Java迭代器警告在上面定义的问题中,我们可以遍历集合,因为它实现了Iterable。
当然,我们可以遍历数据结构并不意味着我们不会遇到任何问题。毕竟,迭代器类有自己的一些规则。也许最重要的规则可以在remove()方法的描述中找到:
从底层集合中移除迭代器返回的最后一个元素(可选操作)。每次调用next()时,只能调用一次此方法。
如果在迭代进行过程中以其他方式(而不是通过调用此方法)修改了基础集合,则未指定迭代器的行为。
8 Java文档(于2020年4月23日捕获)
记得我说过修改NaturalNumber不好,因为可能会导致重复。根据这个定义,修改Set可能会导致不可预知的行为。
当然,这给我提出了一个问题:修改基础集意味着什么?对于Java集合,for-each循环不允许在集合中添加或删除项目。在这些情况下,我们可以看到ConcurrentSpecification异常(DOCS)。
这个错误并不常见。毕竟迭代器怎么知道集合是否被修改过?事实证明,这种行为是被定制到每个集合的next()方法中的。例如,使用List集合,当列表的大小发生变化时,将引发ConcurrentModificationException。换句话说,每次调用next()都会检查数据结构的完整性。
因为集合使用泛型类型,所以不可能考虑不同类型的所有可能情况。因此,在没有跟踪状态的情况下,next()无法检测任何数据是否发生了变异。例如,检查列表中的值是否已经改变可能需要存储先前状态的副本并周期性地检查先前状态。那可不便宜!
更糟糕的是,我们还没有真正讨论修改基础数据对实际迭代过程可能产生的影响。例如,如果next()在某种程度上依赖于底层数据,那么改变它显然会改变后面的内容。
假设我们有一个列表迭代器,它的项目必须实现Comparable。然后,我们以这样的方式构造这个迭代器,它总是按照排序顺序返回下一个值。如果以后要修改基础值,可以创建一个从不遍历整个列表的循环:
[1,2,3]//next()返回1,我们按5进行缩放[5,2,3]//hasnext()声明现在没有其他值,这并不理想。通常,您希望for-each循环实际遍历整个数据结构,但这根本没有做到。
再来说说集合问题。至此,我们有机会从两个不同的角度来讨论集合问题:
如果我们通过生成副本来使集合无效,会发生什么?如果我们通过修改底层数据结构来使for-each循环无效,会发生什么?现在,我想借此机会谈谈问题代码片段执行时可能发生的实际情况:
set natural number nums=new someset implementation();nums . add(new natural number 2(1));nums.add(新自然数字2(5));nums.add(新自然数字2(6));for(natural number n:nums){ n . increment();}假设Set的迭代器没有花哨的修改检测,大多数人期望的是同一个set: {2,6,7}。
另一个可能的结果是,我们得到一个集合,其中只有一些值是递增的。我之前说过,next()方法可能依赖于基础数据来决定下一步做什么。
在这种情况下,我们可以得到增量输出的任意组合:
{2,5,6} {1,6,6} {1,5,7} {2,6,6} {1,6,7}无论哪种情况,我们都不是完全安全的。当然,Set看起来一样,但真的一样吗?
让我们假设这个集合是使用哈希表实现的。这提供了能够快速检查重复的优点,但是它需要更多的维护。例如,如果要更改Set的值,必须重新计算散列并检查冲突。
当我们直接修改NaturalNumber时,我们将跳过这个维护阶段。因此,我们的哈希表仍然包含最初的三个哈希值。例如,当有人检查集合中是否有两个时,该方法将错误地返回false。
当然,这是实现细节。大概根本没发现什么问题。程序继续平稳运行,没有人注意到。然而,就像所有的实现细节一样,我们不能依赖于它们假定的行为。换句话说,程序还是不可预测的。
除了未成年人,Set的Java实现实际上指出了这个确切的问题:
注意:如果你使用可变对象作为集合元素,你必须格外小心。如果对象的值被更改为影响相等比较的方式,并且该对象是集合中的元素,则不指定集合的行为。这种禁止的一个特例是,集合不允许将自身包含为一个元素。
设置文档(2020年4月24日查看)
似乎很难将Set实现集成在一起,而且这个实现没有可变类型的问题。我不知道这是关于可变类型的。
什么是外卖?最后,我认为迭代器文档的编写方式可以让用户乐在其中。换句话说,当它说:
如果在迭代过程中以其他方式(而不是通过调用此方法)修改了基础集合,则不会指定迭代器的行为。
它真正的意思是“以任何方式”。当然我永远无法证实这些怀疑,所以很好奇看看别人怎么说。
同时,如果你喜欢这篇文章,如果你能借此机会学习如何帮助扩展这个网站,我将不胜感激。在那篇文章中,你会了解到我的邮件列表和Patreon。
否则,这里有一些相关的文章给你:
在Java中使用余数运算符以双精度复制变量数据类型时要小心。否则,谢谢你的坚持。希望我的深夜考研对你有用!
翻译:3359 www . javacodegeeks . com/2020/04/be-caring-when-modified-data-while-using-a-Java-iterator . html
Java迭代器退出迭代