머리말
동시 수정 예외는 다음과 같습니다 ConcurrentModificationException
.
foreach 루프는 실제로 향상된 for 루프입니다. for 루프와 비교하여 더 간결하고 배열 또는 컬렉션을 통과하는 데 사용할 수 있습니다. 기본 원리는 iterator의 기능을 실현하는 것이므로 본질적으로 foreach는 모든 것을 통과할 수 있습니다. 구현 Iterable 인터페이스의 객체입니다.
1. foreach의 기본 구현 원칙
foreach는 기본적으로 컴파일러에서 제공하는 "구문 설탕" 래퍼에 지나지 않습니다. 컴파일러가 코드를 만나면 for(Type item : arrayOrList) { }
코드를 번역합니다.
1. foreach를 사용하여 배열을 순회하는 경우 다음 변환이 수행됩니다.
소스 코드:
번역된 코드:
변환된 코드는 실제로 우리가 일반적으로 배열을 트래버스하기 위해 for 루프를 사용하는 방식이라는 것을 알 수 있습니다. 즉, 다음과 같습니다.
for(int i=0;i<array.length;i++) {
Type item = array[i];
System.out.println(item);
}
java.lang.Iterable
2. 컬렉션 유형을 순회하려면 순회된 컬렉션 유형이 인터페이스를 구현하고 iterator()
메서드에서 Iterator 반복자를 반환 해야 합니다 .
소스 코드:
번역된 코드:
foreach를 사용하여 컬렉션을 순회할 때 컬렉션 순회를 실현하기 위해 반복자로 변환된다는 것을 알 수 있습니다. 지금 바로:
Iterator iterator = strings.iterator();
while(iterator.hasNext()){
String string = (String)iterator.next();
System.out.println(string);
}
둘, foreach 사용 제한
- Foreach는 컬렉션 순회 프로세스 중에 컬렉션의 요소 값을 수정할 수 없습니다. 그러나 배열을 순회하는 경우에는 이 제한이 적용되지 않습니다.
- Foreach는 순회 프로세스 중에 컬렉션에 요소를 추가하거나 삭제할 수 없습니다. 그렇지 않으면
ConcurrentModificationException
예외가 발생합니다. 이 예외가 개별적인 특수한 경우에 발생하지 않더라도 우연의 일치 때문입니다(아래 설명 참조). - 순회 프로세스 중에 컬렉션 또는 배열의 한 요소만 동시에 표시됩니다. 즉, "현재 순회한 요소"만 표시되고 이전 또는 후속 요소는 표시되지 않습니다.
- 앞뒤로만 이동할 수 있고 역방향으로는 이동할 수 없습니다.
잘못된 사용의 예
포인트 2와 관련하여 foreach 순회 중에 요소를 추가하거나 제거해 보십시오.
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
final String toRemove = "2";
final String toAdd = "1000";
for (String item : list) {
//item = "100"; //这句执行无效,仅仅改变迭代器中item的指向,并不会真正改变list中的元素
if (toRemove.equals(item)) {
list.remove(item); //仅当toRemove为"3"时,没有报异常。这是删除倒数第二个元素情况下的“巧合”。
//list.add(toAdd); // 报ConcurrentModificationException
}
}
위 상황에서 발생한 동시 수정 예외에 대한 이유 분석: 1. ArrayList 내부에 modCount
목록의 요소가 변경된 횟수를 기록하는 데 사용되는 멤버 변수가 있습니다.
2. iter=list.iterator()
새 반복자 객체를 반환할 때 iter는 expectedModCount
멤버 변수를 사용하여 당시 modCount 값을 기록합니다. 전체 주기를 순회하는 동안 메서드 iter.next()
와 iter.remove()
메서드는 원래 ArrayList의 modCount 값이 iter 내부에 기록된 값과 일치하는지 확인하고 expectedModCount
일치하지 않으면 버립니다 ConcurrentModificationException
. 따라서 컬렉션 요소를 추가하거나 뺀 후 루프의 다음 라운드 iter.next()
메서드에서 예외가 발생합니다 .
3. 관련 소스코드
public boolean hasNext() {
return cursor != size;
}
public E next() {
// 此处抛出异常
checkForComodification();
// 省略其他代码...
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
4. toRemove가 "3"인 경우 예외가 보고되지 않는 이유는 무엇입니까?
이 경우 제거되면 원래 ArrayList의 크기가 1만큼 줄어들고 다음 라운드가 통과됩니다 iter.hasNext()
(hasNext는 반복자의 내부 반복 위치 커서가 반복되는 컨테이너의 크기에 도달했는지 여부를 반환하고 예외를 던지지 않음) 판단 더 이상 요소가 없으면 false를 직접 반환하고 iter.next()
메서드를 호출하지 않습니다. 물론 이 메서드에서 발생하는 예외는 없습니다.
올바르게 사용
따라서 질문은 컬렉션을 순회하는 과정에서 요소를 삭제하거나 추가해야 하는 경우 어떻게 해야 합니까?
1. 일반적인 for 루프
for(int i=0;i<list.size();i++) {
String item = list.get(i);
if ("3".equals(item)) {
list.remove(i);//为了效率,这里最好不要用list.remove(item)
}
}
2. Iterator는 iterator 메서드를 통해 순회하고, iterator의 remove 메서드를 통해 삭제합니다.
Iterator<String> iter = list.iterator();
while(iter.hasNext()) {
String item = iter.next();
if ("4".equals(item)) {
iter.remove();
}
}
요약하다
subList 시나리오에서 상위 컬렉션의 요소 추가 또는 삭제에 세심한 주의를 기울여야 합니다. 그러면 ConcurrentModificationException
하위 목록의 순회, 추가 및 삭제에 이상이 발생할 수 있습니다. ——Alibaba Java 개발 사양(Songshan Edition)