Home » 레퍼런스 » JAVA » java iterator for loop 코드는 어떻게 동작할까요?

java iterator for loop 코드는 어떻게 동작할까요?

java collection을 다루다 보면, iteration 순회 정도는 아무렇지 않게 쓰셨을 겁니다. 이들이 어떠한 순서로 작동하는지 알아봅니다. 그리고, 실제 ArrayList에서 어떻게 동작하는지 같이 알아보도록 하겠습니다. 이 글에서는 어레이 리스트만 설명하나, 다른 collection 에서도 기작은 크게 다르지 않습니다. 자료구조에서 두 메서드의 구현이 다를 뿐입니다.

java 17 버전에서 실행하였음을 염두에 두시면 좋겠습니다.


java iterator for loop 기작

아래 코드를 봅시다. 이 코드가 실행될 때, 어떤 일이 일어날까요?

[그림 1] 예제 1번 프로그램

참조자 무효화가 떠오르는 것 같지만, 이 단락에서는 말하지 않겠습니다. 여기에서는 7번째 for loop가 돌 때 마다, 어떤 일이 일어날까? 에 초점을 먼저 맞춰보겠습니다. iterator에는 2가지 속성의 메서드가 있습니다.

  • hasNext
  • next

먼저 hasNext는 다음 원소가 있는지 검사합니다. next는 다음 원소로 cursor를 옮기게 됩니다. iterator는 처음 초기화가 될 때 중요한 정보들을 초기화 합니다. 이 중에 modCount와 expectedModCount를 동기화 하는 부분도 포함되어 있습니다.

[그림 2] 어디서 연산 횟수를 동기화 할까?

그리고, 매 for loop 마다, 아래와 같은 순서로 메서드를 호출합니다.

hasNext 먼저 호출되고, 그 다음에 next가 호출됩니다.

[그림 3] arrayList의 hasNext 메서드

hasNext 함수는 단순히 다음 원소가 있는지 검사합니다.

[그림 4] arrayList의 next 메서드

next 함수에서는 cursor를 하나 증가시킵니다. 배열에서 다음 원소는 이전 원소 위치에 1을 더하면 되기 때문입니다. 그런데, 이 함수에서는 단순히 cursor만 옮기지 않습니다. 몇 가지 검사를 추가로 하게 됩니다. 이 중 불완전한 상태를 검사하는 부분이 있는데 967번째 줄에서 하게 됩니다.

여기까지 java iterator for loop 기작을 정리해 봅시다.

  • iterator 의 정보가 대상을 기반으로 초기화 됩니다.
    • 특히 modCount 동기화는 중요한 요소입니다.
  • 매 루프마다 hasNext와 next 순서로 호출 됩니다.

이제, 두 개의 예제 프로그램을 보도록 하겠습니다. 예제 1 ~ 2번은 arrayList 에서의 순회를 다룹니다.


예제 1번 따라가 보기

이를 토대로 예제 1번의 흐름을 따라가 봅시다.

[그림 5] 초기화 된 iterator

이 중 중요한 것은 expectedModCount 입니다. 기대하는 원소 크기입니다. 예제 1번에서는 원소를 2개만 넣었으니까 2가 됩니다. 위에서 서술했다시피, hasNext, next 순으로 루프가 돌 때 마다 호출하게 됩니다.

이제 예제 1의 상황을 봅시다. A, B 순서대로 추가했고, 루프를 돌면서 B랑 일치하면 B를 제거합니다.

처음 상황은 이런 상황입니다. cursor가 0이고, size가 2이니 0 != 2입니다. hasNext 조건 통과했습니다. 고로, 다음 원소가 있습니다. 따라서 next 메서드가 수행됩니다. arrayList 에서는 cursor의 값만 하나 증가합니다. 즉, 다음 원소를 가리키게 됩니다.

첫 번째 for loop를 탈 때 초기 상태는 위와 같습니다. A를 순회했고, cursor는 다음 위치인 B를 가리키는 상태입니다. 두 번째 for loop를 탈 때는 어떨까요?

B를 순회했고, cursor는 그 다음 위치인 끝을 가리키게 됩니다. 그런데, 이 상태에서 B를 제거합니다.

그러면 list의 size는 2가 아닌 1이 되어버립니다. 그런데 cursor는 2가 됩니다. cursor != size 이기 때문에 또 next 함수를 타게 됩니다. 이 메소드에서 아래 함수를 호출하게 됩니다.

[그림 6] 문제의 check 메서드

위 메소드는 modCount와 expectedModCount가 다르면 예외를 뱉습니다. expectedModCount는 2입니다. 여기서 modCount는 대상체의 modCount를 의미합니다. 이 둘 중 하나가 어떤 이유로 동기화 되지 않으면 1012번째 줄에 걸릴 수 있습니다.

remove 할 때 상황을 봅시다.

[그림 7] remove 할 때 call stack

call stack을 보면 remove에서 fastRemove를 호출함을 볼 수 있습니다.

[그림 8] fastRemove 함수

이 메소드를 보면, modCount++ 를 하고 있습니다. modCount 횟수를 증가시켜요. 한 쪽만 증가되는 상황입니다.

이 시점에서 invalid 한 상태가 되었습니다. 당연히, 그 이후에 next 를 호출했다면 문제가 되겠지요. 이 이후에 hasNext를 호출했을 때 cursor는 2였지만, size가 1이였습니다.

  • 2 != 1이였고
  • invalid한 상태입니다.

따라서, 예외가 발생합니다.


예제 2번 따라가 보기

아래 예제 2번 코드는 어떨까요?

[그림 9] 예제 2번 프로그램

예외가 발생할 것 같지만 아닙니다. 왜 그런가? 위에서 hasNext 다음에 next가 호출된다고 했습니다. 이터레이터가 invalid한 상태로 변하는 시점부터 봅시다.

먼저, B를 뽑아온 시점에 cursor는 다음 원소인 C를 가리키고 있습니다. 이 때 for loop 안의 if 절에 걸리기 때문에 ma.remove(“B”)를 수행하게 됩니다. 이 구문이 수행되면 아래와 같이 됩니다.

cursor와 size가 같습니다. 고로 hasNext 조건이 맞지 않기 때문에, 루프가 끝나게 됩니다.

valid한 상태인가요? 그렇지 않습니다. 당연하게도, modCount만 증가했기 때문입니다. invalid 상태가 되었음에도 불구하고요. 어떻게 된 일인가요? next가 호출되기 전에 hasNext가 호출되었습니다. 그리고, hasNext에서 False를 리턴해서 끝내버렸습니다. 예외가 발생하기 전에 끝나버린 것입니다.

Leave a Comment

12 + 5 =