[Java] 개념노트 51 Iterator와 컬렉션 반복 이해하기

[Java 개념노트 51] Iterator와 컬렉션 반복 이해하기

안녕하세요. Java 개념노트 시리즈 쉰한 번째 글입니다.

지난 글에서는 MapHashMap을 사용해서 key와 value를 한 쌍으로 저장하고 꺼내는 방법을 정리했습니다. 이번 글에서는 컬렉션에 저장된 값을 하나씩 꺼내는 방법인 Iterator와 컬렉션 반복 방법에 대해 알아보겠습니다.

컬렉션을 사용하면 여러 데이터를 저장할 수 있습니다. 하지만 저장만 해서는 부족합니다. 저장된 값을 하나씩 꺼내서 출력하거나, 조건에 맞는 값을 찾거나, 필요 없는 값을 삭제해야 할 때가 많습니다. 이때 반복문과 Iterator를 사용합니다.


1. 컬렉션 반복이란?

컬렉션 반복은 List, Set, Map 같은 컬렉션에 저장된 값을 하나씩 꺼내서 처리하는 것입니다.

예를 들어 과목 목록이 있을 때 모든 과목을 출력하거나, 상품 목록에서 특정 상품을 찾거나, 중복 없는 태그 목록을 하나씩 확인할 수 있습니다.

쉽게 말하면
컬렉션 반복은 여러 데이터가 들어 있는 상자에서 값을 하나씩 꺼내 확인하는 과정입니다.

2. 컬렉션을 반복하는 대표 방법

컬렉션을 반복하는 방법은 여러 가지가 있습니다. 입문 단계에서는 아래 세 가지를 먼저 익히면 좋습니다.

방법 특징 주로 사용
일반 for문 인덱스를 사용해서 반복합니다. List
향상된 for문 값을 하나씩 편하게 꺼냅니다. List, Set
Iterator 컬렉션 값을 순서대로 꺼내는 반복 도구입니다. List, Set, 삭제가 필요한 반복

3. List는 일반 for문으로 반복할 수 있다

List는 인덱스가 있기 때문에 일반 for문으로 반복할 수 있습니다.

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();

        list.add("Java");
        list.add("Spring");
        list.add("SQL");

        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}

실행 결과는 다음과 같습니다.

Java
Spring
SQL

list.size()로 개수를 확인하고, list.get(i)로 인덱스 위치의 값을 꺼냈습니다.

4. 향상된 for문으로 반복하기

인덱스가 필요 없다면 향상된 for문을 사용할 수 있습니다. 코드가 더 짧고 읽기 쉽습니다.

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();

        list.add("Java");
        list.add("Spring");
        list.add("SQL");

        for (String item : list) {
            System.out.println(item);
        }
    }
}

실행 결과는 다음과 같습니다.

Java
Spring
SQL

for (String item : list)는 list 안의 값을 하나씩 꺼내 item 변수에 담아 반복합니다.

핵심 포인트
값을 단순히 하나씩 읽기만 한다면 향상된 for문이 가장 간단합니다.

5. Set은 일반 for문으로 반복하기 어렵다

Set은 인덱스가 없습니다. 따라서 get(0)처럼 특정 위치의 값을 꺼낼 수 없습니다.

import java.util.HashSet;

public class Main {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();

        set.add("Java");
        set.add("Spring");

        // set.get(0); // 오류 발생
    }
}

Set은 인덱스 접근이 없기 때문에 보통 향상된 for문이나 Iterator로 반복합니다.

6. Set을 향상된 for문으로 반복하기

HashSet은 향상된 for문으로 값을 하나씩 꺼낼 수 있습니다.

import java.util.HashSet;

public class Main {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();

        set.add("Java");
        set.add("Spring");
        set.add("SQL");

        for (String item : set) {
            System.out.println(item);
        }
    }
}

실행 결과는 다음과 비슷합니다.

Java
Spring
SQL

HashSet은 저장 순서를 보장하지 않을 수 있으므로 출력 순서는 달라질 수 있습니다.

7. Iterator란?

Iterator는 컬렉션에 저장된 값을 하나씩 꺼내기 위한 반복 도구입니다.

Iterator는 컬렉션 내부의 값을 처음부터 끝까지 차례대로 이동하면서 꺼낼 수 있게 해줍니다.

컬렉션
[Java, Spring, SQL]

Iterator
  ↓
Java 꺼냄
  ↓
Spring 꺼냄
  ↓
SQL 꺼냄

비유하기
Iterator는 책갈피처럼 컬렉션 안의 값을 하나씩 넘기면서 읽는 도구라고 생각할 수 있습니다.

8. Iterator import하기

Iteratorjava.util 패키지에 있습니다. 사용하려면 import가 필요합니다.

import java.util.Iterator;

ArrayList와 함께 사용한다면 다음처럼 import할 수 있습니다.

import java.util.ArrayList;
import java.util.Iterator;

9. iterator() 메서드

컬렉션에서 Iterator를 얻으려면 iterator() 메서드를 사용합니다.

Iterator<String> iterator = list.iterator();

이 코드는 list를 반복할 수 있는 Iterator 객체를 만든 것입니다.

코드 의미
list.iterator() list를 반복할 Iterator를 가져옵니다.
Iterator<String> String 값을 하나씩 꺼내는 Iterator입니다.
iterator Iterator 변수 이름입니다.

10. hasNext()와 next()

Iterator에서 가장 중요한 메서드는 hasNext()next()입니다.

메서드 역할
hasNext() 다음 값이 있는지 확인합니다.
next() 다음 값을 꺼냅니다.

보통 while문과 함께 사용합니다.

while (iterator.hasNext()) {
    String item = iterator.next();
    System.out.println(item);
}

11. ArrayList를 Iterator로 반복하기

ArrayList를 Iterator로 반복하는 예제를 보겠습니다.

import java.util.ArrayList;
import java.util.Iterator;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();

        list.add("Java");
        list.add("Spring");
        list.add("SQL");

        Iterator<String> iterator = list.iterator();

        while (iterator.hasNext()) {
            String item = iterator.next();

            System.out.println(item);
        }
    }
}

실행 결과는 다음과 같습니다.

Java
Spring
SQL

hasNext()로 다음 값이 있는지 확인하고, next()로 값을 하나씩 꺼냈습니다.

12. HashSet을 Iterator로 반복하기

HashSet도 Iterator를 사용해서 반복할 수 있습니다.

import java.util.HashSet;
import java.util.Iterator;

public class Main {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();

        set.add("Java");
        set.add("Spring");
        set.add("SQL");

        Iterator<String> iterator = set.iterator();

        while (iterator.hasNext()) {
            String item = iterator.next();

            System.out.println(item);
        }
    }
}

실행 결과는 다음과 비슷합니다.

Java
Spring
SQL

HashSet은 인덱스가 없지만 Iterator를 사용하면 값을 하나씩 꺼낼 수 있습니다.

13. Iterator의 실행 흐름

Iterator의 반복 흐름을 정리하면 다음과 같습니다.

Iterator 생성
        ↓
hasNext()로 다음 값이 있는지 확인
        ↓
true이면 next()로 값 꺼내기
        ↓
다시 hasNext() 확인
        ↓
더 이상 값이 없으면 반복 종료

즉, hasNext()는 확인용이고, next()는 실제로 값을 꺼내는 메서드입니다.

14. next()만 계속 호출하면 안 되는 이유

next()는 다음 값을 꺼내는 메서드입니다. 그런데 더 이상 꺼낼 값이 없는데 next()를 호출하면 오류가 발생할 수 있습니다.

ArrayList<String> list = new ArrayList<>();

list.add("Java");

Iterator<String> iterator = list.iterator();

System.out.println(iterator.next());
System.out.println(iterator.next()); // 오류 발생 가능

두 번째 next()를 호출할 때는 더 이상 꺼낼 값이 없습니다. 그래서 NoSuchElementException이 발생할 수 있습니다.

주의
next()를 호출하기 전에는 보통 hasNext()로 다음 값이 있는지 확인해야 합니다.

15. Iterator의 remove()

Iterator에는 remove() 메서드가 있습니다. 이 메서드는 Iterator로 반복하는 중 현재 꺼낸 값을 삭제할 때 사용합니다.

import java.util.ArrayList;
import java.util.Iterator;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();

        list.add("Java");
        list.add("Spring");
        list.add("SQL");

        Iterator<String> iterator = list.iterator();

        while (iterator.hasNext()) {
            String item = iterator.next();

            if (item.equals("Spring")) {
                iterator.remove();
            }
        }

        System.out.println(list);
    }
}

실행 결과는 다음과 같습니다.

[Java, SQL]

Iterator로 반복하는 중 "Spring"을 만나면 iterator.remove()로 안전하게 삭제했습니다.

16. 반복 중 컬렉션 직접 삭제 주의하기

향상된 for문으로 반복하면서 컬렉션을 직접 수정하면 문제가 생길 수 있습니다.

ArrayList<String> list = new ArrayList<>();

list.add("Java");
list.add("Spring");
list.add("SQL");

for (String item : list) {
    if (item.equals("Spring")) {
        list.remove(item); // 오류 발생 가능
    }
}

이런 코드는 ConcurrentModificationException이 발생할 수 있습니다. 반복 중 컬렉션 구조를 직접 바꾸었기 때문입니다.

반복하면서 삭제해야 한다면 Iterator의 remove()를 사용하는 것이 안전합니다.

17. Iterator remove() 사용 규칙

Iterator의 remove()를 사용할 때도 규칙이 있습니다. next()로 값을 한 번 꺼낸 뒤에 remove()를 호출해야 합니다.

올바른 예

if (iterator.hasNext()) {
    String item = iterator.next();
    iterator.remove();
}

잘못된 예

Iterator<String> iterator = list.iterator();

iterator.remove(); // next() 전에 호출하면 오류 발생 가능

remove()는 마지막으로 next()가 꺼낸 값을 삭제하는 메서드입니다. 따라서 next() 없이 먼저 호출하면 문제가 생길 수 있습니다.

18. Map은 직접 Iterator를 사용하지 않는다

MapList, Set과 구조가 다릅니다. Map은 key-value 쌍을 저장하므로 Map 자체를 바로 Iterator로 반복하기보다는 keySet()이나 entrySet()을 사용합니다.

Map 반복 방법

1. keySet()으로 key를 반복한다.
2. entrySet()으로 key-value 쌍을 반복한다.
3. values()로 value만 반복한다.

19. keySet()과 Iterator

keySet()은 Map의 모든 key를 Set으로 반환합니다. 따라서 keySet에 Iterator를 사용할 수 있습니다.

import java.util.HashMap;
import java.util.Iterator;

public class Main {
    public static void main(String[] args) {
        HashMap<String, Integer> scores = new HashMap<>();

        scores.put("Java", 90);
        scores.put("Spring", 85);
        scores.put("SQL", 80);

        Iterator<String> iterator = scores.keySet().iterator();

        while (iterator.hasNext()) {
            String key = iterator.next();
            Integer value = scores.get(key);

            System.out.println(key + ": " + value);
        }
    }
}

실행 결과는 다음과 비슷합니다.

Java: 90
Spring: 85
SQL: 80

HashMap은 순서를 보장하지 않을 수 있으므로 출력 순서는 달라질 수 있습니다.

20. entrySet()과 Iterator

Map에서 key와 value를 함께 반복할 때는 entrySet()을 많이 사용합니다.

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        HashMap<String, Integer> scores = new HashMap<>();

        scores.put("Java", 90);
        scores.put("Spring", 85);
        scores.put("SQL", 80);

        Iterator<Map.Entry<String, Integer>> iterator = scores.entrySet().iterator();

        while (iterator.hasNext()) {
            Map.Entry<String, Integer> entry = iterator.next();

            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }
}

실행 결과는 다음과 비슷합니다.

Java: 90
Spring: 85
SQL: 80

entry.getKey()로 key를 꺼내고, entry.getValue()로 value를 꺼냈습니다.

핵심 포인트
Map에서 key와 value를 함께 반복할 때는 entrySet()이 자주 사용됩니다.

21. values()로 value만 반복하기

key가 필요 없고 value만 필요하다면 values()를 사용할 수 있습니다.

import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        HashMap<String, Integer> scores = new HashMap<>();

        scores.put("Java", 90);
        scores.put("Spring", 85);
        scores.put("SQL", 80);

        for (Integer score : scores.values()) {
            System.out.println(score);
        }
    }
}

실행 결과는 다음과 비슷합니다.

90
85
80

22. Iterator와 향상된 for문 비교

Iterator와 향상된 for문은 둘 다 컬렉션을 반복할 수 있습니다. 차이를 정리하면 다음과 같습니다.

구분 향상된 for문 Iterator
코드 길이 짧고 간단함 조금 더 길다
읽기 반복 편리함 가능함
반복 중 안전한 삭제 직접 삭제하면 문제 가능 remove() 사용 가능
인덱스 필요 여부 인덱스 사용 안 함 인덱스 사용 안 함

23. Iterator 주요 메서드 정리

Iterator에서 자주 사용하는 메서드는 다음과 같습니다.

메서드 역할 예시
hasNext() 다음 값이 있는지 확인 iterator.hasNext()
next() 다음 값을 꺼냄 iterator.next()
remove() 마지막으로 꺼낸 값을 삭제 iterator.remove()

24. Iterator 전체 예제

이번에는 Iterator를 사용해서 목록을 반복하고 특정 값을 삭제하는 전체 예제를 보겠습니다.

import java.util.ArrayList;
import java.util.Iterator;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> subjects = new ArrayList<>();

        subjects.add("Java");
        subjects.add("Spring");
        subjects.add("SQL");
        subjects.add("Oracle");

        Iterator<String> iterator = subjects.iterator();

        while (iterator.hasNext()) {
            String subject = iterator.next();

            if (subject.equals("SQL")) {
                iterator.remove();
            }
        }

        System.out.println(subjects);
    }
}

실행 결과는 다음과 같습니다.

[Java, Spring, Oracle]

Iterator로 반복하면서 "SQL"을 만나면 안전하게 삭제했습니다.

25. Iterator 사용 시 자주 하는 실수

Iterator를 처음 사용할 때는 아래와 같은 실수를 자주 합니다.

실수 문제점 해결 방법
import 누락 Iterator 클래스를 찾지 못함 import java.util.Iterator; 작성
hasNext() 없이 next() 호출 더 이상 값이 없으면 오류 가능 while (iterator.hasNext()) 사용
향상된 for문 안에서 컬렉션 직접 삭제 ConcurrentModificationException 가능 Iterator의 remove() 사용
next() 전에 remove() 호출 삭제할 대상이 없어 오류 가능 next() 후 조건에 맞을 때 remove()
Map 자체를 바로 Iterator로 반복하려고 함 Map은 key-value 구조라 직접 방식이 다름 keySet(), entrySet() 사용

26. 직접 연습해보기

아래 코드를 직접 작성하고 실행해보세요.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> menus = new ArrayList<>();

        menus.add("김밥");
        menus.add("라면");
        menus.add("돈가스");
        menus.add("라면");

        Iterator<String> menuIterator = menus.iterator();

        while (menuIterator.hasNext()) {
            String menu = menuIterator.next();

            if (menu.equals("라면")) {
                menuIterator.remove();
            }
        }

        System.out.println("라면 삭제 후: " + menus);

        HashMap<String, Integer> priceMap = new HashMap<>();

        priceMap.put("김밥", 3500);
        priceMap.put("돈가스", 9000);

        for (Map.Entry<String, Integer> entry : priceMap.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue() + "원");
        }
    }
}

실행 결과는 다음과 비슷합니다.

라면 삭제 후: [김밥, 돈가스]
김밥: 3500원
돈가스: 9000원

Iterator를 사용해서 반복 중 "라면"을 안전하게 삭제했습니다. Map은 entrySet()으로 key와 value를 함께 반복했습니다.

27. 이번 글 정리

이번 글에서는 컬렉션 값을 하나씩 꺼내는 Iterator와 컬렉션 반복 방법에 대해 정리했습니다. 핵심 내용은 다음과 같습니다.

  • 컬렉션 반복은 컬렉션에 저장된 값을 하나씩 꺼내 처리하는 것이다.
  • List는 인덱스가 있으므로 일반 for문으로 반복할 수 있다.
  • List와 Set은 향상된 for문으로 간단하게 반복할 수 있다.
  • Set은 인덱스가 없으므로 get(0)처럼 접근할 수 없다.
  • Iterator는 컬렉션 값을 하나씩 꺼내는 반복 도구이다.
  • iterator() 메서드로 Iterator 객체를 얻을 수 있다.
  • hasNext()는 다음 값이 있는지 확인한다.
  • next()는 다음 값을 꺼낸다.
  • remove()는 Iterator가 마지막으로 꺼낸 값을 삭제한다.
  • 반복 중 컬렉션을 안전하게 삭제하려면 Iterator의 remove()를 사용할 수 있다.
  • Map은 keySet(), values(), entrySet()을 사용해서 반복한다.
  • Map에서 key와 value를 함께 반복할 때는 entrySet()이 자주 사용된다.

한 줄 요약
Iterator는 컬렉션의 값을 하나씩 안전하게 꺼내는 반복 도구이며, 반복 중 삭제가 필요할 때 특히 유용합니다.

다음 글 예고
다음 글에서는 [Java 개념노트 52] 제네릭이란 무엇인가?라는 주제로 컬렉션에서 <String>, <Integer>처럼 타입을 지정하는 이유를 자세히 정리해보겠습니다.

GWDEVELBlog Java 개념노트 시리즈