foreach 루프의 기본 원칙 및 올바른 사용

머리말

동시 수정 예외는 다음과 같습니다 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.Iterable2. 컬렉션 유형을 순회하려면 순회된 컬렉션 유형이 인터페이스를 구현하고 iterator()메서드에서 Iterator 반복자를 반환 해야 합니다 .

소스 코드:

그림

번역된 코드:

그림

foreach를 사용하여 컬렉션을 순회할 때 컬렉션 순회를 실현하기 위해 반복자로 변환된다는 것을 알 수 있습니다. 지금 바로:

Iterator iterator = strings.iterator();
while(iterator.hasNext()){
    
    
 String string = (String)iterator.next();
 System.out.println(string);
}

둘, foreach 사용 제한

  1. Foreach는 컬렉션 순회 프로세스 중에 컬렉션의 요소 값을 수정할 수 없습니다. 그러나 배열을 순회하는 경우에는 이 제한이 적용되지 않습니다.
  2. Foreach는 순회 프로세스 중에 컬렉션에 요소를 추가하거나 삭제할 수 없습니다. 그렇지 않으면 ConcurrentModificationException예외가 발생합니다. 이 예외가 개별적인 특수한 경우에 발생하지 않더라도 우연의 일치 때문입니다(아래 설명 참조).
  3. 순회 프로세스 중에 컬렉션 또는 배열의 한 요소만 동시에 표시됩니다. 즉, "현재 순회한 요소"만 표시되고 이전 또는 후속 요소는 표시되지 않습니다.
  4. 앞뒤로만 이동할 수 있고 역방향으로는 이동할 수 없습니다.
잘못된 사용의 예

포인트 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)

추천

출처blog.csdn.net/qq_43842093/article/details/131606031