background
Yesterday, Xiao Feng received an interview call from a company. One of the interview questions was a little interesting. I'll share it with you here. The interview question is how to delete the specified element in ArrayList. At first glance, it sounds like a simple question, but if you don't actually step on the pit, you can easily fall into the trap of the interviewer. Let's analyze it together.
problem analysis
Full of doubts
When Xiao Feng heard the interview question, he thought, what is this surface examiner, how to ask such a simple question, and thought that a for loop plus equal judgment and then delete would be finished? But on second thought, no, there must be a trap in it, otherwise I wouldn't ask such a seemingly simple question. Xiao Feng suddenly remembered that he had encountered this problem when writing code before. He also deleted the specified element in the ArrayList, but threw an exception when he directly remove d the element of the for loop. The interviewer's trap is estimated to be here. Xiaofeng secretly rejoiced and found the trap buried by the interviewer.
Xiaofeng recalled the test situation of the day, and the code was desensitized. At the beginning, he wanted to delete the specified elements in the ArrayList. Xiao Feng wrote the following code heartily and confidently. He clicked the Run code button. As a result, he was embarrassed and threw exceptions.
public class TestListMain { public static void main(String[] args) { List<String> result = new ArrayList<>(); result.add("a"); result.add("b"); result.add("c"); result.add("d"); for (String s : result) { if ("b".equals(s)) { result.remove("b"); } } } }
A big red exception will come out immediately. OMG, how can this happen? I feel that there is no problem with the code. Hurry to see what exceptions are thrown and where they are thrown. You can see that an exception of ConcurrentModificationException is thrown, and it is thrown in a detection method in the Itr class. What's going on? We didn't have this Itr code in our original code, so we couldn't figure it out.
restore justice
Since we can't find out from the source code analysis, let's look at the content of the class file compiled by the source code. After all, the class file is the code really executed by the JVM. I don't know if I don't see it. I'm surprised to see that JDK used to play like this. I see. The for each statement in our original code is actually executed by an iterator after compilation.
public class TestListMain { public TestListMain() { } public static void main(String[] args) { List<String> result = new ArrayList(); result.add("a"); result.add("b"); result.add("c"); result.add("d"); //Create iterator Iterator var2 = result.iterator(); while(var2.hasNext()) { String s = (String)var2.next(); if ("b".equals(s)) { result.remove("b"); } } } }
Itr, an internal class iterator created by ArrayList, then the for each loop is transformed into an iterator plus a while loop. The original for each loop is sold for dog meat.
public Iterator<E> iterator() { return new Itr(); }
Itr, an internal class iterator, judges whether the iterator has content by judging hasNext(), and the next() method obtains the content in the iterator.
private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; Itr() {} public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } ... }
The general process is as follows:
The real exception throwing place is this detection method. When modCount and expectedModCount are not equal, exceptions are thrown directly. Let's see what modCount and expectedModCount are respectively. Here, modCount represents the modification times of ArrayList, while expectedModCount represents the modification times of iterator. When creating Itr iterator, modCount is assigned to expectedModCount, Therefore, in this example, both modCount and expectedModCount are 4 at the beginning (the String element is added four times). However, after getting the b element, the ArrayList performs the remove operation, so the modCount is accumulated to 5. Therefore, there is inconsistency during the inspection, which eventually leads to the exception. Here we find the reason for throwing the exception. The loop uses the iterator, but the operation element uses the ArrayList operation Therefore, the iterator throws an exception when it finds that the element has been modified during the loop.
Let's think about it again. Why is there such a test? What role does this exception play? Let's start with the description of the comment of ConcurrentModificationException. The simple understanding is that one thread is not allowed to modify the collection and another thread is not allowed to iterate on the basis of the collection. Once this situation is detected, an exception will be thrown through the fast fail mechanism to prevent the following unknowable situation.
/** *** * For example, it is not generally permissible for one thread to modify a Collection * while another thread is iterating over it. In general, the results of the * iteration are undefined under these circumstances. Some Iterator * implementations (including those of all the general purpose collection implementations * provided by the JRE) may choose to throw this exception if this behavior is * detected. Iterators that do this are known as <i>fail-fast</i> iterators, * as they fail quickly and cleanly, rather that risking arbitrary, * non-deterministic behavior at an undetermined time in the future. *** **/ public class ConcurrentModificationException extends RuntimeException { ... }
Review the whole process
How to delete correctly
Since the reason for throwing the exception is that the iterator is recycled, and deleting the use of ArrayList causes the detection to fail. Then we will use iterators for recycling and iterators for deletion, so as to ensure consistency.
public class TestListMain { public static void main(String[] args) { List<String> result = new ArrayList<>(); result.add("a"); result.add("b"); result.add("c"); result.add("d"); Iterator<String> iterator = list.iterator(); while (iterator .hasNext()) { String str = iterator.next(); if ("b".equals(str)) { itr.remove(); } } }
summary
This paper mainly analyzes the source code of the exceptions in the element deletion of ArrayList in the for loop, which is also a common interview trap question during the interview. The interviewer examines the candidate's mastery of the JDK source code through such a seemingly simple question.
Hello, I'm Mufeng. Thank you for your praise, collection and comments. The article continues to be updated. See you in the next issue!
Wechat search: Mufeng's technical notes and high-quality articles are constantly updated. We have a group of learning to punch in, which can pull you in and work together to impact the big factory. In addition, there are a lot of learning and interview materials for you. Recently, we are sending year-end benefits. Come and have a look.
The true master always has the heart of an apprentice