remove/add the element in the foreach loop

Posted by Doug G on Thu, 14 Oct 2021 03:06:36 +0200

Do not remove/add elements in a foreach loop

1. For the remove element, use the Iterator method. If concurrent operations occur, the Iterator object needs to be locked

// A code block
List<String> a = new ArrayList<String>();
a.add("1");
a.add("2");
for (String temp : a) {
if ("1".equals(temp)) {
a.remove(temp);
}
}

Note: if 1 in if ("1". equals(temp)) {is replaced by 2, the following error will be reported

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at com.song.pojo.Test01.test01(Test01.java:28)
	at com.song.pojo.Test01.main(Test01.java:17)

**Reason: * * therefore, the reason why the ConcurrentModificationException exception is thrown is because the foreach loop is used in the code. In the foreach loop, the collection traversal is carried out through the iterator, but the element add/remove directly uses the collection class's own method, which leads to the fact that the iterator will find an element unknowingly during traversal If it is added / deleted, an exception will be thrown to prompt the user that concurrent modifications may have occurred.

2. Solution?

2-1. Directly use ordinary for loop traversal
Because the ordinary for loop does not use Iterator traversal, there is no fail fast test at all
2-2. Directly use Iterator to traverse
How to directly use the add/remove method provided by the iterator to modify the value of expectedModCount so that this exception will not be thrown. The implementation code is as follows:

public void remove() {
	if (lastRet < 0)
		throw new IllegalStateException();
	checkForComodification();
	try {
		ArrayList.this.remove(lastRet);
		cursor = lastRet;
 		lastRet = -1;
		expectedModCount = modCount;
	} catch (IndexOutOfBoundsException ex) {
		throw new ConcurrentModificationException();
	}
 }

2-3. Filter using the filter provided in Java 8:
In Java 8, you can convert a collection into a Stream. There is a filter operation for the Stream. You can test the original Stream, and the elements that pass the test are left to generate a new Stream:

public static void main(String[] args) {
		List<String> userNames = new ArrayList<String>();
		userNames.add("Tom");
		userNames.add("Jack");
		userNames.add("Mic");
		userNames.add("Seven");
		userNames = userNames.stream().filter(userName -> !userName.equalsIgnoreCase("Jack")).collect(Collectors.toList());
		System.out.println(userNames);
	}

2-4. Set classes that directly use fail safe:
In Java, in addition to some ordinary collection classes, there are also some collection classes that adopt the fail safe mechanism. Such collection containers are accessed directly on the collection content from time to time during traversal, but first copy the original collection content and traverse the copied collection.
Because the iteration is to traverse the copy of the original set, the modifications made to the original set during the iteration cannot be detected by the iterator, so the ConcurrentModificationException will not be thrown.

public static void main(String[] args) {
		ConcurrentLinkedDeque<String> userNames = new ConcurrentLinkedDeque<String>();
		userNames.add("Tom");
		userNames.add("Jack");
		userNames.add("Mic");
		userNames.add("Seven");
		for (String userName : userNames) {
			if (userName.equals("Jack")) {
				userNames.remove(userName);
			}
		}
		System.out.println(userNames);
	}

The advantage of copying content is to avoid the ConcurrentModificationException, but similarly, the iterator cannot access the modified content, that is, the iterator traverses the set copy obtained at the moment when it starts traversing, and the iterator does not know the modification of the original set during traversal.

The above methods can avoid triggering the fail fast mechanism and throwing exceptions. If it is a concurrent scenario, it is recommended to use the container in the concurrent package; if it is a single thread scenario, it is recommended to use Iterator to delete / add elements in the code before Java 8; after Java 8, you can consider using Stream and filter

Topics: Java