210225_B책_순회하며 컬렉션 수정하지 않기

정재현·2021년 2월 25일
0

TIL

목록 보기
77/80

오늘은 순회하며 작업하는 내용을 살펴본다.
실제로 업무 시에 자주 사용했던 내용이며, 어떤 한 리스트를 돌며 찾거나 제거하는 내용을 자주 사용하곤 했다.
코드에서는 항상 배열이나 리스트를 비롯해 다양한 자료 구조를 순회하는데,
자료구조를 바꾸려면 조심해야한다. 프로그램이 충돌할 위험이 있기때문인데.
먼저 예제코드를 보자면,

class Inventory {
 private List<Supply> supplies = new ArrayList<>();
 
 void disposeContaminatedSupplies() {
  for (Supply supply : supplies) {
   if (supply.isContaminated()) {
    supplies.remove(supply);
   }
  }
 }
}

보다시피 코드는 그리 어렵지 않고 문제될 것도 없어보인다.
supply list를 만들어 그 안에 supply를 집어넣고 돌리면서 만약 오염된 supply가 있다면 그것을 지워준다.
코드는 괜찮아 보이지만 재고 목록 내 한 supply라도 오염되었을 경우 무조건 충돌한다.
supplies.remove(supply); 이부분에서 말이다.
=> (충돌하는지는 몰랐다;)
그런데 충돌도 문제지만 더 큰문제는 충돌하기 전까지는 이 코드가 잘못된 코드인지 모른다는점.
더군다나 만약 오염된 supply가 없다면? 그 상태 그대로 배포에 나가는 것이다.
모든 제품이 깨끗하니 문제없이 동작할테고, 버그를 발견하기 어려워진다.

순회하는 for 루프 내 remove는 List 인터페이스 표준 구현이나 Set이나 Queue와 같은 Collection 인터페이스 구현은
ConcurrentModificationException을 던진다. List를 순회하며 List를 수정할 수 없다는 이야기.
해당 예외는 Collection을 순회하는동안 그 컬렉션을 수정한다는 이야기인데, 그것이 안되니
어떤 방식을 써야하냐면
더 나은 해결책은 오염된 제품을 찾고 "그 후" 앞에서 발견했던 제품을 모두 제거하는 방법이다.
먼저 순회 -> 나중 제거

잘 동작은 하지만 코드 몇 줄이 더 필요하다, 게다가 순회하는 동안 오염된 제품을 임시 자료 구조에
저장해야한다. 시간과 메모리가 더 드는 것!

class Inventory {
 private List<Supply> supplies = new ArrayList<>();
 
 void disposeContaminatedSupplies() {
  while (iterator.hasNext()) {
   if (iterator.next().isContaminated()) {
    iterator.remove();
   }
  }
 }
}

Iterator는 첫 번째 원소부터 시작해 리스트 내 원소를 가리키는 포인터처럼 동작한다.
hasNext()를 통해 원소가 남아있는지 묻고 next()로 다음 원소를 얻고
반환된 마지막 원소를 remove()로 안전하게 제거한다.
List를 직접 수정할 수는 없지만 iterator가 이것을 완벽히 대신한다.
iterator는 순회중에도 모든 작업을 올바르게 수행하니깐.
사실 for-each문도 iterator에 기반한다고 한다.
=> (iterator의 존재는 예전부터 알았지만 항상 for문 과의 차이점에서 헷갈리곤 해서 구글링 했었는데 이번에 확실하게 알게되었다.)

오늘의 코멘트: 근데 if문을 사용할 때 remove가 안된다는 것은 이번에 처음알았다..

profile
"돈받고 일하면 프로다"

0개의 댓글