Error
Situation
Code
public class Main { public static void main(String[] args) { List<String> listA = new ArrayList<>(); init(listA); testAdd(listA); testRemove(listA); } private static void init(List<String> list) { list.add("a"); list.add("b"); list.add("c"); } private static void testAdd(List<String> list) { for(String s : list) { if(s.equals("a")) { list.add("z"); // 순환 중인 list 에 요소 삽입 } } } private static void testRemove(List<String> list) { for(String s : list) { if(s.equals("a")) { list.remove(s); // 순환 중인 list의 요소 삭제 } } } }
Solution
1. for-each 대신 for 문으로 순회
// 1. for 문으로 순회 - add private static void testAdd(List<String> list) { for(int i=0; i< list.size(); i++) { if(list.get(i).equals("a")) { list.add("z"); } } } public static void main(String[] args) { List<String> listA = new ArrayList<>(); init(listA); testAdd(listA); System.out.println(listA); }
문제 없이 메서드가 동작하며
[a, b, c, z]
가 출력됨.
// 1. for 문으로 순회 - remove private static void testAdd(List<String> list) { for(int i=0; i< list.size(); i++) { if(list.get(i).equals("a")) { list.remove(i); } } } public static void main(String[] args) { List<String> listA = new ArrayList<>(); init(listA); testRemove(listA); System.out.println(listA); }
add와 같은 방법으로 해결 가능하며,
[b, c]
가 출력됨.
// 1. for 문으로 순회 - LinkedList public class Main { public static void main(String[] args) { List<String> listB = new LinkedList<>(); init(listB); testAdd(listB); testRemove(listB); System.out.println(listB); } private static void init(List<String> list) { list.add("a"); list.add("b"); list.add("c"); } private static void testAdd(List<String> list) { for(int i=0; i< list.size(); i++) { if(list.get(i).equals("a")) { list.add("z"); } } } private static void testRemove(List<String> list) { for(int i=0; i< list.size(); i++) { if(list.get(i).equals("a")) { list.remove(i); } } } }
ArrayList와 같은 방법으로 해결 가능하며,
[b, c, z]
가 출력됨.
2. break 로 loop 탈출
// 2. break 로 loop 탈출 private static void testAdd(List<String> list) { for(String s : list) { if(s.equals("a")) { list.add("z"); // 순환 중인 list 에 요소 삽입 break; } } } private static void testRemove(List<String> list) { for(String s : list) { if(s.equals("a")) { list.remove(s); // 순환 중인 list 에 요소 삽입 break; } } } public static void main(String[] args) { List<String> listA = new ArrayList<>(); init(listA); testAdd(listA); testRemove(listA); System.out.println(listA); }
문제 없이 메서드가 동작하며
[b, c, z]
가 출력됨.
3. removeIf 메서드 활용
// 3. removeIf 메서드 활용 public static void main(String[] args) { List<String> listA = new ArrayList<>(); List<String> listB = new LinkedList<>(); init(listA); init(listB); listA.removeIf(s -> s.equals("a")); listB.removeIf(s -> s.compareTo("b") > 0); System.out.println(listA); System.out.println(listB); } private static void init(List<String> list) { list.add("a"); list.add("b"); list.add("c"); list.add("a"); list.add("d"); }
문제 없이 메서드가 동작하며
[b, c, d] / [a, b, a]
가 출력됨.
Additional
에러가 어떤 조건에서 발생하는 지 알아보기 위해 다양한 경우의 수를 테스트 해보았다.
그 과정에서 List.size() - 1번째에 해당하는 요소를 순회하고 있을 때 임의의 요소 하나를 삭제할 경우에는 exception 이 발생하지 않는다는 것을 알게되었다.
Condition
s.equals("c")
일 때는 에러가 발생하지 않는다.s.equals("c")
가 아닌 s.equals("a")
, s.equals("b")
, s.equals("d")
일 때는 모두 ConcurrentModificationException 이 발생한다.public static void main(String[] args) { List<String> listA = new ArrayList<>(); init(listA); testRemove(listA); System.out.println(listA); } private static void init(List<String> list) { list.add("a"); list.add("b"); list.add("c"); list.add("d"); } // exception 이 발생하지 않는 경우1 - 요소 하나 삭제 (자신) private static void testRemove(List<String> list) { for(String s : list) { if(s.equals("c")) { list.removeIf(s); } } } // exception 이 발생하지 않는 경우2 - 요소 하나 삭제 (임의의 다른 요소) private static void testRemove(List<String> list) { for(String s : list) { if(s.equals("c")) { list.remove(0); } } } // exception 이 발생하는 경우1 - 요소 여러개 삭제 private static void testRemove(List<String> list) { for(String s : list) { if(s.equals("c")) { list.removeIf(s1 -> s1.compareTo("c") < 0); } } } // exception 이 발생하는 경우2 - 리스트 크기-1번째가 아닌 경우 private static void testRemove(List<String> list) { for(String s : list) { if(s.equals("a")) { list.remove(s); } } }
Cause
remove() 후 list에 남아있는 요소가 있는지 확인하는 hasNext()에서 list의 size와 현재 커서의 index 값을 확인했을 때 더 이상 남은 element가 없음으로 인식하면서 해당 loop가 종료되기 때문이다.