java迭代器退出迭代_使用Java迭代器修改数据时要小心

java迭代器退出迭代

随着本学期的结束,我想我会分享一个关于如何非常熟悉Java迭代器的小故事。

现实世界语境

就上下文而言,我开设了第二年软件组件课程,这是尝试进入该专业的学生的最后障碍。 当然,这门课程对学生来说压力很大,我经常必须加倍努力,为他们提供一切成功的机会。

不幸的是,本学期我们被大流行所笼罩,不得不转换为在线教学。 结果,我们不得不对教学做出一些快速决策,从而改变了学生的学习方式。 尤其是,我们将所有纸笔考试转换为在线测验。

对于某些学生来说,这是一个很大的祝福。 毕竟,这些测验并没有比考试更困难,因此我们将其设为公开考试。 换句话说,我们使课程变得更容易让他们通过。

当然,学生遍布世界各地,他们无法获得所需的帮助。 此外,学生没有像考试那样认真地学习。 这种结合创造了一些非常糟糕的测验分数。

到我们进行第四个测验时,学生们已经非常沮丧。 实际上,我从几位教师那里听说,他们的学生已经厌倦了“技巧性问题”。 作为一名讲师,听到这有些令人沮丧,因为它们是非常典型的考试问题。 我们并没有为他们增加困难,但这是我第一次听到这些抱怨。

示例问题

然后,发生了一些奇怪的事情。 我们给了他们一个我真的不知道答案的问题,结果有点类似以下内容:

以下代码片段后面的Set <NaturalNumber> nums变量的值是什么?

 Set<NaturalNumber> nums = new SomeSetImplementation<>();  nums.add( new NaturalNumber2( 1 ));  nums.add( new NaturalNumber2( 5 ));  nums.add( new NaturalNumber2( 6 ));  for (NaturalNumber n : nums) { n.increment();  } 

当然,学生的选择如下:

  • nums = {1,5,6,2,6,7}
  • nums = {2,6,7}
  • nums = {1,5,6}
  • 从提供的信息中无法分辨。

现在,就上下文而言,此示例中有一些内部组件。

首先,NaturalNumber是一个可变类,它表示无界的非负整数。 换句话说,NaturalNumber的范围可以从零到无穷大。 此外,可以使用一系列基本数学运算来修改NaturalNumber,如下所示:

  • increment()加1 this
  • add(NaturalNumber n) :将n添加this

此外,这个问题让使用的Set是类似于一个数学集合。 这里的想法是Set具有两个主要属性:

  1. Set缺少重复项(即{1,2,1}不是合法集合)。
  2. Set是无序的(即{1,2,3}和{3,2,1}是等效的)。

作为参考,如果您有兴趣详细信息,请在课程网站上完整地记录这两个组件 。 所有组件都是使用“按合同设计”编写的,因此每种方法都将包含一个适当的合同,其中前置条件用@requires表示,后置条件用@ensures表示。

此外,我们使用@ restores,@ updates,@ clears和@replaces等参数模式标记每个参数。 当然,这超出了本文的范围。

解决问题

现在,我重申一下,我一开始不确定确切的答案是正确的。 显然,第一个答案(即{1、5、6、2、6、7})是错误的,因为增加基础值不会为Set添加新值-或我认为。 使用相同的逻辑,我还假设第三组(即{1,5,6})显然是不正确的,因为我们显然是在改变基础值。

在这一点上,我相当有信心第二个答案(即{2,6,7})是正确的,因为我的学生有87%是正确的。 当然,我有答案键,所以我不得不挑战自己以理解为什么正确答案实际上是最终答案(即“无法从提供的信息中分辨出来。”)。

现在,根据本文的标题,您可能已经领先于我。 没关系! 但是,我没有立即得出这个结论。 相反,我退后一步,决定实际绘制Set

当然,当您尝试这样做时会遇到几个主要问题。 首先,正如我之前提到的, Set没有顺序。 结果,我们如何推断哪个元素在迭代过程中首先出现? 我们会尝试所有可能的配置吗?

这些是我还没有准备好应对的问题。 幸运的是,事实证明,按外观顺序进行迭代可以节省很多时间。 看一看:

 { 1 , 5 , 6 } // Initial state  { 2 , 5 , 6 } // After incrementing the first element  { 2 , 6 , 6 } // After incrementing the second element 

哦! 我们打破了第一条规则: Set不得包含重复项。 因此,我们无法确定结果Set将是什么样。 我的最终答案是D:“无法从提供的信息中分辨出来。”

不幸的是,这种解释并不令我满意。 就像,我知道Set不能包含重复项,但是打破该规则的实际后果是什么? 换句话说,如果情况如此糟糕,我们为什么还要让用户访问基础数据呢?

我认为,用户仅应在删除数据后才能访问数据。 总的来说,我认为图书馆在这方面做得很好。 如果Set没有实现Iterable ,那么我们将Iterable

Java迭代器简介

这给我带来了一个更奇怪的问题:Java迭代器。 为了使此代码起作用, Set必须实现Iterable,这意味着为基础体系结构定义一个Iterator。

现在,如果您曾经编写自己的迭代器,那么您就需要执行以下操作:

 new Iterator<T>() { @Override public boolean hasNext() { ... } @Override public T next() { ... } @Override public void remove() { ... }  } 

在这里,基本思想是我们定义某种可以充当惰性数据结构的结构。 如果您熟悉其他语言(例如Python)的生成器表达式 ,则有相同的想法:我们创建了一个对象,该对象可以从一系列项目中一次返回一个项目。

在实践中, Iterator工作方式是继续通过next()方法提供项,直到没有返回值为止( 这可能永远不会发生 )。 在有界序列中,我们知道何时停止,因为hasNext()方法将返回false 。 这些方法一起可以作为循环机制的核心:

 while (iter.hasNext()) { T item = next();  } 

通过使一个类实现Iterable ,我们可以利用一些Java语法糖,称为for-each循环:

 for (T item: collection) { ... } 

Java迭代器警告

在上面定义的问题中,我们能够遍历Set因为它实现了Iterable

当然,仅因为我们能够遍历数据结构并不意味着我们不会遇到任何问题。 毕竟, Iterator类具有一些自己的规则。 也许最重要的规则可以在remove()方法的描述中找到:

从基础集合中移除此迭代器返回的最后一个元素(可选操作)。 每次调用next()只能调用一次此方法。 如果在迭代进行过程中以其他方式(而不是通过调用此方法)修改了基础集合,则未指定迭代器的行为。

Java 8文档 (捕获于04/23/2020)

记住我曾说过修改NaturalNumber是不好的,因为它可能导致重复。 好吧,基于此定义,无论如何修改Set都可能导致不可预测的行为。

当然,这对我提出了一个问题: 修改基础集合意味着什么。 对于Java集合,for-each循环不允许从集合中添加或删除项目。 在这些情况下,我们可以期望看到ConcurrentModificationException ( docs )。

现在,该错误并不普遍。 毕竟, Iterator如何知道集合是否已被修改? 事实证明,该行为是自定义地烘焙到每个集合的next()方法中的。 例如,使用List集合, 当列表的大小更改时,抛出 ConcurrentModificationException 。 换句话说,每次调用next()都会检查数据结构的完整性。

由于集合利用泛型类型,因此不可能考虑可能出现的所有不同类型的情况。 结果, next()无法检测是否有任何数据在没有跟踪状态的情况下发生了变异。 例如,检查列表中的值是否已更改可能需要存储先前状态的副本并定期检查该先前状态。 那不便宜!

更糟的是,我们还没有真正讨论修改基础数据对实际迭代过程可能产生的影响。 例如,如果next()以某种方式依赖于基础数据,则对其进行更改将明显地改变接下来的内容。

想象一下,我们有一个用于列表的Iterator ,其项目必须实现Comparable 。 然后,我们以使其始终按排序顺序返回下一个值的方式制作此Iterator 。 如果要随后修改基础值,则可以创建一个永不遍历整个列表的循环:

 [ 1 , 2 , 3 ] // next() returns 1 which we scale by 5  [ 5 , 2 , 3 ] // hasNext() claims there are no other values 

现在,这并不理想。 通常,您希望for-each循环实际上遍历整个数据结构,而这根本没有做到这一点。

再谈集合问题

在这一点上,我们有机会从两个不同的角度来讨论Set问题:

  1. 如果我们通过生成重复项来使Set无效,会发生什么情况?
  2. 如果我们通过修改基础数据结构使for-each循环无效,会发生什么情况?

现在,我想借此机会谈谈在执行问题代码片段时可能发生的实际情况:

 Set<NaturalNumber> nums = new SomeSetImplementation<>();  nums.add( new NaturalNumber2( 1 ));  nums.add( new NaturalNumber2( 5 ));  nums.add( new NaturalNumber2( 6 ));  for (NaturalNumber n : nums) { n.increment();  } 

假设SetIterator没有花哨的修改检测,则大多数人期望的结果可能是相同的Set :{2,6,7}。

另一个可能的结果是我们得到一个Set ,其中仅某些值递增。 就像我之前说过的那样, next()方法可能取决于基础数据来决定下一步做什么。

在这种情况下,我们可能会得到增量输出的任何组合:

  • {2,5,6}
  • {1,6,6}
  • {1,5,7}
  • {2,6,6}
  • {2,5,7}
  • {1,6,7}

无论哪种情况,我们都不是完全安全的。 当然, Set看起来一样,但是真的一样吗?

让我们想象一下,该Set是使用哈希表实现的。 这提供了能够快速检查重复项的优点,但是需要更多的维护。 例如,如果要更改Set的值,则必须重新计算哈希并检查冲突。

当我们直接修改NaturalNumber ,我们将跳过此维护阶段。 结果,我们的哈希表仍将包含原始的三个哈希。 例如,当有人检查Set中是否包含两个时,该方法将错误地返回false

当然,这是一个实现细节。 很可能根本没有发现任何问题。 该程序继续平稳运行,没有人注意。 但是,与所有实现细节一样,我们不能依赖于它们的假定行为。 换句话说,该程序仍然是不可预测的。

除了未成年人, Set的Java实现实际上指出了这个确切的问题:

注意:如果将可变对象用作集合元素,则必须格外小心。 如果对象的值更改为影响相等比较的方式,而该对象是集合中的元素,则不指定集合的​​行为。 此禁止的一种特殊情况是,不允许集合包含自身作为元素。

Java Set文档 (查看04/24/2020)

看起来很难将Set实现集成在一起,而该实现没有可变类型的问题。 我不知道那是关于可变类型的...

什么是外卖?

最后,我认为Iterator文档的编写方式让用户可以玩得开心。 换句话说,当它说:

如果在迭代进行过程中以其他方式(而不是通过调用此方法)修改了基础集合,则未指定迭代器的行为。

它的真正含义是“ 以任何方式” 。 当然,我永远无法证实这些怀疑,所以我很想看看其他人怎么说。

同时,如果您喜欢这篇文章,那么如果您能借此机会学习如何帮助扩大该网站 ,我将不胜感激。 在那篇文章中,您将了解我的邮件列表以及Patreon。

否则,这是一些适合您的相关文章:

  • 余数运算符在Java中用于Doubles
  • 复制可变数据类型时要小心

否则,感谢您的坚持。 希望我深夜的研究生学习对您有用!

翻译自: https://www.javacodegeeks.com/2020/04/be-careful-when-modifying-data-while-using-a-java-iterator.html

java迭代器退出迭代

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/331198.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pojo 带参构造函数_带有Java Pojo作为输入输出示例的AWS Lambda函数

pojo 带参构造函数在上一个教程中&#xff0c;我们看到了如何使用Java创建AWS Lambda函数&#xff0c;我们传递了String作为输入&#xff0c;还返回了String作为Output。如果您是第一次创建lambda函数&#xff0c;我建议先阅读该教程。 在本教程中&#xff0c;我们将看到如何传…

MySQL检索数据(过滤+通配符+正则表达式)

【0】README0.1&#xff09;本文部分文字描述转自“MySQL 必知必会”&#xff0c;旨在review “MySQL的基础知识”&#xff1b;【1】检索数据1&#xff09;检索单个列&#xff1a;select a_name from table_name;2&#xff09;检索多个列&#xff1a;select a_name,b_name from…

创建者模式

转载自 设计模式之创建者模式创建者模式又叫建造者模式&#xff0c;是将一个复杂的对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。创建者模式隐藏了复杂对象的创建过程&#xff0c;它把复杂对象的创建过程加以抽象&#xff0c;通过子类继承或者重…

buildpack_使用Buildpack容器化Spring Boot应用程序

buildpack在本文中&#xff0c;我们将看到如何使用Buildpacks容器化Spring Boot应用程序。 在先前的一篇文章中&#xff0c;我讨论了Jib 。 Jib允许我们在不使用Dockerfile的情况下将任何Java应用程序构建为Docker映像。 现在&#xff0c;从Spring Boot 2.3开始&#xff0c;我们…

MySQL创建字段+数据处理函数+汇总数据(聚集函数)+分组数据

【0】README0.1&#xff09;本文部分文字描述转自“MySQL 必知必会”&#xff0c;旨在review“MySQL创建字段数据处理函数汇总数据&#xff08;聚集函数&#xff09;分组数据” 的基础知识&#xff1b;【1】创建计算字段1&#xff09;problemsolution1.1&#xff09;problem&am…

apache.camel_Apache Camel 3.2 – Camel的无反射配置

apache.camel在Apache Camel项目中&#xff0c;我们正在努力开发下一个即将发布的Apache Camel 3.2.0版本。 我们在Camel 3中努力研究的问题之一就是使其变得更小&#xff0c;更快。 其中一个方面是配置管理。 您可以按照12要素原则以多种方式完全配置Camel&#xff0c;以使配…

MySQL数据检索+查询+全文本搜索

【0】README0.1&#xff09;本文部分文字描述转自“MySQL 必知必会”&#xff0c;旨在review“MySQL数据检索查询全文本搜索” 的基础知识&#xff1b;【1】使用子查询1&#xff09;查询定义&#xff1a;任何sql 语句都是查询。但此术语一般指 select语句&#xff1b;SQL 还允许…

selenium自动化测试_49自动化测试中最常见的Selenium异常

selenium自动化测试开发人员将始终在编写代码时牢记不同的场景&#xff0c;但是在某些情况下&#xff0c;实现可能无法按预期工作。 相同的原则也适用于测试代码&#xff0c;该代码主要用于测试现有产品的功能&#xff0c;发现bug并确保产品100&#xff05;不受bug影响。 正确…

MySQL的CRUD操作+使用视图

【0】README0.1&#xff09;本文部分文字描述转自“MySQL 必知必会”&#xff0c;旨在review“MySQL数据检索查询全文本搜索” 的基础知识&#xff1b;【1】插入数据1&#xff09;insert是用来插入&#xff08;或添加&#xff09;行到数据库表的。插入可以用以下几种方式使用&a…

mega x_[MEGA DEAL] 2020年完整的Java Master Class Bundle(96%)

mega x通过超过62个小时的培训来掌握最流行的编程语言&#xff0c;从而树立信誉良好的开发人员职业 嘿&#xff0c;怪胎&#xff0c; 这一周&#xff0c;我们JCG促销专区 &#xff0c;我们有另一个极端的报价 。我们正在提供一个巨大的96&#xff05;off的完整2020 Python编程…

tomcat(11)org.apache.catalina.core.StandardWrapper源码剖析

【0】README0.0&#xff09;本文部分文字描述转自 “how tomcat works”&#xff0c;旨在学习 “tomcat(11)StandardWrapper源码剖析” 的基础知识&#xff1b;0.1&#xff09;StandardWrapper 是 Catalina中对Wrapper接口的标准实现&#xff1b;要知道&#xff0c;tomcat 中有…

lambda表达式语法_使用类似Lambda的语法作为Java中的表达式进行切换

lambda表达式语法从Java 14开始&#xff0c; switch表达式具有其他Lambda式 &#xff08; case ... -> labels &#xff09;语法&#xff0c;它不仅可以用作语句&#xff0c;还可以用作计算为单个值的表达式。 对于新的类似Lambda的语法&#xff0c;如果标签匹配&#xff0…

装饰器模式(讲解+应用)

转载自 设计模式&#xff08;5&#xff09;装饰器模式&#xff08;讲解应用&#xff09;目录 装饰器模式为什么使用装饰器模式应用实例 装饰器模式 看到装饰器是在看《Thinking in Java》一书的时候&#xff0c;看到文件读写那边的时候&#xff0c;有提到装饰器模式&#xff0c…

quarkus_使用Quarkus调试容器中的系统测试(视频)

quarkus如果您能够借助容器在本地进行端到端测试应用程序&#xff0c;则可以提高开发效率。 在下面的视频中&#xff0c;我将展示如何使用Quarkus在Docker容器中调试本地系统测试。 这是我关于有效测试的视频课程的Quarkus扩展。 要全面了解&#xff0c;还可以查看以下资源&a…

MySQL事务管理+安全管理+MySQL数据类型

【0】README0.1&#xff09;本文部分文字描述转自“MySQL 必知必会”&#xff0c;旨在review“MySQL事务管理安全管理MySQL数据类型” 的基础知识&#xff1b;【1】管理事务处理【1.1】事务处理1&#xff09;并非所有引擎都支持事务管理&#xff0c;MyISAM 不支持&#xff0c;而…

超音速 启动_从根本上讲超音速亚原子Enterprise Java

超音速 启动我创建了一个视频&#xff0c;其中我用Quarkus解释了“超音速亚原子Java”&#xff0c;这是现代Java应用程序的运行时。 无论您是刚开始涉足Enterprise Java领域&#xff0c;还是已经是一位经验丰富的Java EE / J2EE开发人员&#xff0c;本课程都将指导您如何在2020…

利用java求积分(定积分和无穷限积分)

【0】README0.1&#xff09;本文部分文字描述转自或译自 https://en.wikipedia.org/wiki/Simpson%27s_rule和 https://en.wikipedia.org/wiki/Numerical_integration#Methods_for_one-dimensional_integrals&#xff1b;旨在利用java求积分&#xff1b;&#xff08;定积分和无穷…

Java的三种代理模式

转载自 Java的三种代理模式1.代理模式 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.这里使用到编程中的一个思想:不要随意去修改别人已经写好的…

后台审核管理 ergo_Kogito,ergo规则:从知识到服务,轻松自如

后台审核管理 ergo欢迎阅读有关Kogito倡议的博客系列的另一集&#xff0c;以及我们将Drools带入云的努力。 这些文章的目的是收集用户对我们提供给Kogito的功能的早期反馈。 在本文中&#xff0c;我们介绍了两种实现完整智能服务的新方法 &#xff1a; 独立的规则服务 集成智…

java转换文本文件到xlsx(自制缓冲区,无需先验文件行数)

【0】README0.1&#xff09;本文代码利用了 Apache POI 框架 建立 java 到 xlsx 代码的联系&#xff1b;0.2&#xff09;本文自制缓冲区从文本文件中读取数据读取&#xff0c;无需先验文件行数&#xff1b;0.3&#xff09;本文通过缓冲区大小创建数组&#xff0c;数组容量自动增…