问题引入:
在Java语言中,我们可以通过以下的语法结构进行对容器内元素的遍历(以字符串为例):
for(String word : Words) {
implement ···
}
这个操作是之前的C语言中没有定义的操作,其内部实现原理是通过一个Iterator,即迭代器进行实现的,具体实现如下:
Iterator iter = Words.iterator();
while (iter.hasNext()) {
String word = iter.next();
implement ···;
}
使用迭代器后,类似于链表的原理,迭代器不断执行操作,直到没有下一个元素为止。
但是当遇到遍历元素进行删除时,例如下一段代码(来自于讲义第四章的例子)
public static void dropCourse6(ArrayList<String> subjects) {
MyIterator iter = new MyIterator(subjects);//自定义的迭代器功能
while (iter.hasNext()) {
String subject = iter.next();
if ( subject.startWith("6.")) {
subjects.remove(subject);//如果课程以"6."开头,则删除
}
}
}
当执行测试用例时,发现与预期结果不符?
dropCourse6(["6.045","6.005","6.813"])
//expected [], actual ["6.005"]
问题分析:
按照函数的功能,三门课程都应该被删除,而有一门却没有被删除。要想解决这个问题,就要深入迭代器的实现原理以及分析方法流程。
分析函数流程,在刚开始的时候,迭代器指向的下标是0,也就是这个容器内的第一个元素。识别到第一个字符串时,发现是以"6."开头的,容器执行删除操作。迭代器的list加1,但是此时容器的第一个元素被删除,原来的第2,3个元素递补到了第1,2个元素,但是此时迭代器指向的是第2个元素,也就是原本的第3个元素,所以第2个元素被略过去了,导致没有被删除,而是被留在了集合中。
推广到更多的元素,如果仍旧使用这种在容器内的方法删除,就会导致有越来越多的元素被略过去,从而产生非常严重的错误。
解决方法:
不应该在容器内删除元素,而是应该利用容器内的元素的mutable特性,利用迭代器,指向要删除的元素进行删除,避免了因为下标改变造成的影响。具体代码如下:
Iterator iter = subjects.iterator();
while (iter.hasNext()) {
String subject = iter.next();
if (subject.startWith("6.")) {
iter.remove();
}
}
问题总结:
迭代器的实现主要是利用了List,Map,Set这种容器类接口的mutable特性,从而让迭代器和容器内的元素指向同一空间进行操作得以实现。
得到的启示:
执行删除操作时出现了小问题,看似正确的代码在执行时也会出现出乎意料的结果,所以就要求我们要修炼内功,练就一双火眼金睛,能够发现隐藏的小漏洞。道阻且长,仍需努力!
——2022.06.09 By Cosmic-