λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
⌨️ Language/Java

[Java] “κ·Έλƒ₯ forλ¬Έ μ“°λ©΄ μ•ˆ λΌμš”?” Java 순회 방식 μ œλŒ€λ‘œ μ•ŒκΈ°

by hyebin (Helia) 2025. 6. 13.
λ°˜μ‘ν˜•

 


λͺ©μ°¨

     

    “μ»¬λ ‰μ…˜μ— 데이터λ₯Ό λ„£μ—ˆλŠ”λ°, μ–΄λ–»κ²Œ κΊΌλ‚΄μ•Ό ν• μ§€ λͺ¨λ₯΄κ² μ–΄μš”.”
    “forλ¬Έ μ“°λ‹€ κ°‘μžκΈ° μ˜ˆμ™Έκ°€ ν„°μ‘ŒλŠ”λ° 이유λ₯Ό λͺ¨λ₯΄κ² μ–΄μš”.”

     

    Javaλ₯Ό 처음 λ°°μš°κ±°λ‚˜ 싀무에 λ“€μ–΄μ˜¨ 초보 κ°œλ°œμžλ“€μ΄ κ°€μž₯ 자주 ν˜Όλž€μŠ€λŸ¬μ›Œν•˜λŠ” 것 쀑 ν•˜λ‚˜κ°€ μ»¬λ ‰μ…˜ 순회 λ°©μ‹μž…λ‹ˆλ‹€.

    Iterator, for-each, ListIterator, forEach(), Stream APIκΉŒμ§€… λ„λŒ€μ²΄ 뭐가 λ‹€λ₯΄κ³ , μ–Έμ œ 뭘 써야 ν• κΉŒμš”?

     

    이번 κΈ€μ—μ„œλŠ” μžλ°” μ»¬λ ‰μ…˜ 순회의 λͺ¨λ“  방식을 예제 μ½”λ“œ μ€‘μ‹¬μœΌλ‘œ λΉ„κ΅ν•˜κ³ , μ‹€λ¬΄μ—μ„œ 자주 ν„°μ§€λŠ” μ˜ˆμ™Έ μƒν™©κΉŒμ§€ μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€.


    βœ… μ™œ μ»¬λ ‰μ…˜ 순회 방식이 μ΄λ ‡κ²Œ λ§Žμ„κΉŒ?

    JavaλŠ” κΎΈμ€€νžˆ λ°œμ „ν•΄ μ˜€λ©΄μ„œ κ°œλ°œμžλ“€μ˜ μš”κ΅¬μ™€ 기술 흐름에 맞좰 λ‹€μ–‘ν•œ 순회 방식을 λ„μž…ν•΄ μ™”μŠ΅λ‹ˆλ‹€.

    각 방식은 κ·Έ μ‹œμ μ—μ„œ λΆˆνŽΈν•¨μ„ ν•΄μ†Œν•˜κ±°λ‚˜ 생산성을 높이기 μœ„ν•΄ λ“±μž₯ν•œ 것이죠.

    버전 κΈ°λŠ₯ νŠΉμ§•
    Java 1.2 Iterator μ•ˆμ „ν•œ μ‚­μ œ
    Java 5 for-each κ°„κ²°ν•œ 문법
    Java 8 forEach(), Stream ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ° μŠ€νƒ€μΌ

     

    ➑️ λͺ¨λ‘ μ—¬μ „νžˆ μ‚¬μš© κ°€λŠ₯ν•˜λ©°, 각 방식은 상황에 맞좰 선택해야 졜적의 μ½”λ“œ ν’ˆμ§ˆμ„ λ‚Ό 수 μžˆμŠ΅λ‹ˆλ‹€.


    πŸ”Ή Iterator

    IteratorλŠ” μ»¬λ ‰μ…˜μ˜ μš”μ†Œλ₯Ό 순차적으둜 탐색할 수 μžˆλŠ” ν‘œμ€€ μΈν„°νŽ˜μ΄μŠ€μž…λ‹ˆλ‹€.

    특히 순회 쀑 μš”μ†Œλ₯Ό μ•ˆμ „ν•˜κ²Œ μ œκ±°ν•  수 μžˆλŠ” μœ μΌν•œ λ°©λ²•μž…λ‹ˆλ‹€.

    List<String> items = new ArrayList<>(List.of("사과", "μƒν•œ λ°”λ‚˜λ‚˜", "우유"));
    
    Iterator<String> iterator = items.iterator();
    while (iterator.hasNext()) {
        String item = iterator.next();
        if (item.contains("μƒν•œ")) {
            iterator.remove();  // μ•ˆμ „ν•œ 제거!
        }
    }

     

    πŸ“Œ νŠΉμ§• 및 μž₯점

    • μš”μ†Œ μ œκ±°μ— μ•ˆμ „ (ConcurrentModificationException λ°©μ§€)
    • λͺ¨λ“  Collectionμ—μ„œ λ™μΌν•œ λ°©μ‹μœΌλ‘œ μ‚¬μš© κ°€λŠ₯
    • λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰μ΄ 효율적 (특히 λŒ€μš©λŸ‰ 데이터 순회 μ‹œ)


    ⚠️ 단점

    • μ½”λ“œκ°€ μž₯황함
    • remove()λŠ” next() 호좜 μ΄ν›„μ—λ§Œ μ‚¬μš© κ°€λŠ₯
    • List.of() 같은 λΆˆλ³€ μ»¬λ ‰μ…˜μ—μ„œλŠ” μ˜ˆμ™Έ λ°œμƒ


    πŸ› οΈ μ–Έμ œ μ‚¬μš©ν• κΉŒ?

    • 순회 쀑 μš”μ†Œλ₯Ό μ œκ±°ν•΄μ•Ό ν•  λ•Œ
    • μ™ΈλΆ€ APIμ—μ„œ 받은 μ»¬λ ‰μ…˜μ΄ λΆˆν™•μ‹€ν•  λ•Œ
    • λ©”λͺ¨λ¦¬ λ―Όκ°ν•œ ν™˜κ²½μ—μ„œ λŒ€λŸ‰ 데이터 처리 μ‹œ 

    πŸ” for-each

    for-eachλŠ” Java 5λΆ€ν„° λ„μž…λœ ν–₯μƒλœ forλ¬Έμž…λ‹ˆλ‹€.

    μ½”λ“œκ°€ κ°„κ²°ν•˜κ³  가독성이 μ’‹μ•„, λŒ€λΆ€λΆ„μ˜ 순회 μž‘μ—…μ—μ„œ μ‚¬μš©λ©λ‹ˆλ‹€

    List<String> fruits = List.of("사과", "λ°°", "포도");
    
    for (String fruit : fruits) {
        System.out.println(fruit);
    }

     

    πŸ“Œ νŠΉμ§• 및 μž₯점

    • κ°€μž₯ 직관적인 순회 문법
    • 가독성이 λ›°μ–΄λ‚˜ ν˜‘μ—… μ½”λ“œμ— 유리
    • λ°°μ—΄, List, Set, Map λ“± λͺ¨λ“  μ»¬λ ‰μ…˜μ— 적용 κ°€λŠ₯
    • 인덱슀 μ‹ κ²½ μ“Έ ν•„μš” μ—†μŒ

     

    ⚠️ 단점

    • 인덱슀 μ ‘κ·Ό λΆˆκ°€
    • μš”μ†Œ 제거 μ‹œ ConcurrentModificationException λ°œμƒ
    for (String item : list) {
        if (item.equals("B")) {
            list.remove(item);  // ❌ μ˜ˆμ™Έ λ°œμƒ
        }
    }

     

    πŸ› οΈ μ–Έμ œ μ‚¬μš©ν• κΉŒ? 

    • λ‹¨μˆœν•˜κ²Œ λͺ¨λ“  μš”μ†Œλ₯Ό μ²˜λ¦¬ν•  λ•Œ
    • 가독성이 μ€‘μš”ν•œ ν”„λ‘œμ νŠΈμ—μ„œ
    • μš”μ†Œ μ‚­μ œκ°€ ν•„μš” μ—†λŠ” 경우

    πŸ”Έ 전톡적인 forλ¬Έ

    μ΅μˆ™ν•œ for (int i = 0; i <...; i++) λ°©μ‹μœΌλ‘œ, 인덱슀λ₯Ό 기반으둜 μ„Έλ°€ν•œ μ œμ–΄κ°€ ν•„μš”ν•œ 경우 μœ μš©ν•©λ‹ˆλ‹€.

    List<String> songs = Arrays.asList("λ…Έλž˜1", "λ…Έλž˜2", "λ…Έλž˜3");
    
    for (int i = 0; i < songs.size(); i++) {
        System.out.println(i + 1 + "번째: " + songs.get(i));
    }

     

    πŸ“Œ νŠΉμ§• 및 μž₯점

    • 인덱슀 ν™œμš© κ°€λŠ₯ (μˆœμ„œ ν•„μš”ν•  λ•Œ κ°•λ ₯)
    • μ—­μˆœ 순회, κ±΄λ„ˆλ›°κΈ° λ“± 자유둜운 흐름

     

    ⚠️ 단점

    • LinkedListμ—μ„œλŠ” μ„±λŠ₯ μ €ν•˜ κ°€λŠ₯ (get(i)κ°€ O(n))
    LinkedList<String> list = new LinkedList<>();
    for (int i = 0; i < list.size(); i++) {
        list.get(i);  // 맀번 μ²˜μŒλΆ€ν„° 탐색!
    }

     

     

    πŸ› οΈ μ–Έμ œ μ‚¬μš©ν• κΉŒ?

    • 인덱슀 기반 접근이 ν•„μš”ν•œ 경우
    • ArrayList와 같은 랜덀 μ ‘κ·Ό μ»¬λ ‰μ…˜μ—μ„œ
    • νŠΉμ • νŒ¨ν„΄(짝수 인덱슀만, μ—­μˆœ λ“±)이 ν•„μš”ν•œ 경우

    πŸ”„ forEach λ©”μ„œλ“œ

    Java 8λΆ€ν„° μΆ”κ°€λœ forEach λ©”μ„œλ“œλŠ” λžŒλ‹€ ν‘œν˜„μ‹κ³Ό ν•¨κ»˜ μ‚¬μš©λ˜λ©°, κ°„λ‹¨ν•œ λ‘œμ§μ„ λΉ λ₯΄κ²Œ μ μš©ν•  λ•Œ μœ μš©ν•©λ‹ˆλ‹€.

    List<Product> products = getProductList();
    
    products.forEach(product -> {
        if (product.isOnSale()) {
            product.applyDiscount(0.2);
        }
    });

     

     

    πŸ“Œ νŠΉμ§• 및 μž₯점

    • λžŒλ‹€μ‹κ³Ό μžμ—°μŠ€λŸ½κ²Œ κ²°ν•©
    • Map.forEach((key, value) -> {}) 등에도 ν™œμš© κ°€λŠ₯

     

    ⚠️ 단점

    • break, continue λΆˆκ°€λŠ₯
    • μ˜ˆμ™Έ 처리 μ‹œ try-catch ν•„μˆ˜

     

    πŸ› οΈ μ–Έμ œ μ‚¬μš©ν•˜λ‚˜?

    • κ°„λ‹¨ν•œ 순회 + 좜λ ₯/처리
    • λžŒλ‹€μ‹ 기반의 ν•¨μˆ˜ν˜• μŠ€νƒ€μΌλ‘œ μž‘μ„±ν•  λ•Œ

    🌊 Stream API ν™œμš©

     

    Stream은 μ»¬λ ‰μ…˜ 데이터λ₯Ό ν•¨μˆ˜ν˜• μŠ€νƒ€μΌλ‘œ μ²˜λ¦¬ν•  수 있게 ν•΄μ£ΌλŠ” APIμž…λ‹ˆλ‹€.

    필터링, λ³€ν™˜, 집계, 병렬 처리 λ“± λ³΅μž‘ν•œ μž‘μ—…μ— μ ν•©ν•©λ‹ˆλ‹€.

    double avg = students.stream()
        .filter(s -> s.getMajor().equals("CS"))
        .mapToDouble(Student::getGpa)
        .average()
        .orElse(0.0);

     

    πŸ“Œ νŠΉμ§• 및 μž₯점

      • μ„ μ–Έν˜• 처리 → 가독성 λ†’μŒ
      • parallelStream()으둜 병렬 처리 지원
      • filter, map, reduce λ“± μ—°μ‚° κ°€λŠ₯

     

    ⚠️ 단점

    • break/continue μ‚¬μš© λΆˆκ°€
    • ν•™μŠ΅ 곑선 쑴재
    • κ³Όλ„ν•˜κ²Œ μ“°λ©΄ 였히렀 λ³΅μž‘ν•΄μ§

     

    πŸ› οΈ μ–Έμ œ μ‚¬μš©ν• κΉŒ?

    • 데이터λ₯Ό 필터링, 가곡, 집계할 λ•Œ
    • 병렬 처리둜 속도 ν–₯상이 ν•„μš”ν•œ 경우
    • μ½”λ“œμ˜ μ„ μ–Έμ„±κ³Ό 가독성을 높이고 싢을 λ•Œ

    ⚠️  μžμ£Ό λ§Œλ‚˜λŠ” μ˜ˆμ™Έ: ConcurrentModificationException

    ConcurrentModificationException은 μ»¬λ ‰μ…˜μ„ 순회 쀑일 λ•Œ, κ·Έ μ»¬λ ‰μ…˜μ˜ ꡬ쑰λ₯Ό λ™μ‹œμ— μˆ˜μ •ν•˜λ©΄ λ°œμƒν•˜λŠ” λŸ°νƒ€μž„ μ˜ˆμ™Έμž…λ‹ˆλ‹€.

     

    즉, λ‹€μŒκ³Ό 같은 μƒν™©μ—μ„œ λ°œμƒν•©λ‹ˆλ‹€:

    • for-each λ˜λŠ” Iteratorλ₯Ό 톡해 순회 쀑일 λ•Œ
    • remove(), add() λ“±μœΌλ‘œ μš”μ†Œλ₯Ό 직접 λ³€κ²½ν•  경우
    List<String> names = new ArrayList<>(List.of("Tom", "Jane", "Mike"));
    
    for (String name : names) {
        if (name.startsWith("J")) {
            names.remove(name);  // ❌ μ˜ˆμ™Έ λ°œμƒ
        }
    }

     

    ⚠️ μ™œ λ°œμƒν• κΉŒ?

    μžλ°”μ˜ λŒ€λΆ€λΆ„μ˜ μ»¬λ ‰μ…˜ ν΄λž˜μŠ€λŠ” λ‚΄λΆ€μ μœΌλ‘œ “ꡬ쑰 λ³€κ²½ μ‹œ fail-fast” λ©”μ»€λ‹ˆμ¦˜μ„ μ μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

    즉, 순회 도쀑 μ»¬λ ‰μ…˜μ˜ ν¬κΈ°λ‚˜ ꡬ쑰가 λ³€κ²½λ˜λ©΄ μ¦‰μ‹œ μ˜ˆμ™Έλ₯Ό 던져 버그λ₯Ό 쑰기에 κ°μ§€ν•˜κ²Œ ν•΄ μ€λ‹ˆλ‹€.

    λΉ λ₯΄κ²Œ μ‹€νŒ¨(fail-fast)ν•¨μœΌλ‘œμ¨, 순회 결과의 λΆˆμΌμΉ˜λ‚˜ 버그λ₯Ό λ°©μ§€ν•˜λŠ” λ©”μ»€λ‹ˆμ¦˜μž…λ‹ˆλ‹€.

     

    βœ… μ•ˆμ „ν•˜κ²Œ μ œκ±°ν•˜λŠ” 4κ°€μ§€ 방법

    1️⃣ Iterator.remove() μ‚¬μš©

    Iteratorλ₯Ό μ‚¬μš©ν•  경우, next()둜 κΊΌλ‚Έ μš”μ†Œμ— λŒ€ν•΄ remove() λ©”μ„œλ“œλ₯Ό μ΄μš©ν•΄ μ•ˆμ „ν•˜κ²Œ μ‚­μ œν•  수 μžˆμŠ΅λ‹ˆλ‹€.

    List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
    
    Iterator<Integer> it = numbers.iterator();
    while (it.hasNext()) {
        Integer num = it.next();
        if (num % 2 == 0) {
            it.remove();  // μ•ˆμ „ν•˜κ²Œ 제거
        }
    }
    System.out.println(numbers);  // [1, 3, 5]
    πŸ’‘ λ°˜λ“œμ‹œ next()λ₯Ό ν˜ΈμΆœν•œ 후에 remove()λ₯Ό 써야 ν•©λ‹ˆλ‹€.

     

     

    2️⃣ removeIf() (Java 8 이상)

    μ»¬λ ‰μ…˜μ˜ removeIf(Predicate) λ©”μ„œλ“œλ₯Ό μ΄μš©ν•˜λ©΄ λžŒλ‹€μ‹ 기반으둜 쑰건을 λ§Œμ‘±ν•˜λŠ” μš”μ†Œλ§Œ μ œκ±°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

    List<String> words = new ArrayList<>(List.of("apple", "banana", "avocado", "berry"));
    
    words.removeIf(word -> word.startsWith("a"));
    
    System.out.println(words);  // [banana, berry]
    κ°„κ²°ν•˜κ³  μ‹€λ¬΄μ—μ„œ 자주 μ“°μž…λ‹ˆλ‹€.

     

     

    3️⃣ μ—­λ°©ν–₯  forλ¬Έ μ‚¬μš©

    μ•žμ—μ„œλΆ€ν„° μˆœνšŒν•˜λ©΄μ„œ μ‚­μ œν•˜λ©΄ μΈλ±μŠ€κ°€ λ°€λ €μ„œ 였λ₯˜κ°€ λ°œμƒν•  수 μžˆμœΌλ―€λ‘œ, λ’€μ—μ„œλΆ€ν„° 거꾸둜 μˆœνšŒν•˜λ©΄μ„œ μ œκ±°ν•©λ‹ˆλ‹€.

    List<String> items = new ArrayList<>(List.of("A", "B", "C", "B", "D"));
    
    for (int i = items.size() - 1; i >= 0; i--) {
        if (items.get(i).equals("B")) {
            items.remove(i);
        }
    }
    
    System.out.println(items);  // [A, C, D]
    특히 인덱슀 기반 μ‚­μ œκ°€ ν•„μš”ν•œ 경우 νš¨κ³Όμ μž…λ‹ˆλ‹€.

     

     

    4️⃣  Stream.filter()둜 μƒˆ μ»¬λ ‰μ…˜ 생성

    원본 μ»¬λ ‰μ…˜μ„ λ³€κ²½ν•˜μ§€ μ•Šκ³ , 쑰건을 λ§Œμ‘±ν•˜λŠ” μš”μ†Œλ§Œ 남긴 μƒˆλ‘œμš΄ 리슀트λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.

    List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
    
    List<Integer> oddNumbers = numbers.stream()
        .filter(n -> n % 2 != 0)
        .collect(Collectors.toList());
    
    System.out.println(oddNumbers);  // [1, 3, 5]
    원본을 μœ μ§€ν•΄μ•Ό ν•˜κ±°λ‚˜ λΆˆλ³€ μ»¬λ ‰μ…˜(List.of λ“±)을 λ‹€λ£° λ•Œ μœ μš©ν•©λ‹ˆλ‹€.

     

     

    πŸ” Tip

    • for-eachλ‚˜ stream() μ•ˆμ—μ„œλŠ” μ ˆλŒ€ remove() ν•˜μ§€ 말 것
    • 쑰건에 따라 μ‚­μ œκ°€ ν•„μš”ν•˜λ©΄ → removeIf() / Iterator.remove() / μ—­λ°©ν–₯ for
    • λΆˆλ³€ 리슀트인 경우 → μƒˆλ‘œμš΄ μ»¬λ ‰μ…˜μœΌλ‘œ Stream.filter()둜 μž¬μƒμ„±

    πŸ“Š 상황별 μΆ”μ²œ κ°€μ΄λ“œ

    상황 μΆ”μ²œ λ°©μ‹ 이유
    μš”μ†Œ 제거 Iterator, removeIf() μ•ˆμ „ν•˜κ²Œ 제거 κ°€λŠ₯
    λ‹¨μˆœ 순회 for-each 직관적, 가독성 우수
    인덱슀 기반 μ ‘κ·Ό forλ¬Έ μ»€μŠ€ν„°λ§ˆμ΄μ§• 용이
    데이터 가곡 Stream μœ μ—°ν•˜κ³  선언적 처리 κ°€λŠ₯
    병렬 처리 parallelStream CPU ν™œμš© κ·ΉλŒ€ν™”
    λžŒλ‹€ μŠ€νƒ€μΌ forEach() κ°„κ²°ν•œ ν‘œν˜„

    πŸ™Œ 마무리

    “μ»¬λ ‰μ…˜μ„ κΊΌλ‚΄ μ“°λŠ” 법을 μ•Œλ©΄, μ½”λ“œμ˜ ν’ˆμ§ˆμ΄ 달라진닀.”

     

    μžλ°”μ˜ μ»¬λ ‰μ…˜ 순회 방식은 λ‹¨μˆœν•œ 문법 차이가 μ•„λ‹ˆλΌ, μ„±λŠ₯κ³Ό μ•ˆμ „μ„±, μ½”λ“œ 가독성에 μ§κ²°λ˜λŠ” μ€‘μš”ν•œ μ„ νƒμž…λ‹ˆλ‹€.

    • λ‹¨μˆœ μˆœνšŒμ—λŠ” for-each
    • μΈλ±μŠ€κ°€ ν•„μš”ν•  땐 전톡적인 for
    • μš”μ†Œ μ‚­μ œκ°€ ν•„μš”ν•˜λ‹€λ©΄ Iterator λ˜λŠ” removeIf
    • μ„ μ–Έν˜• μŠ€νƒ€μΌμ΄λ‚˜ 병렬 μ²˜λ¦¬μ—λŠ” Stream

    각 방식이 λ„μž…λœ μ΄μœ μ™€ μ“°μž„μƒˆλ₯Ό μ΄ν•΄ν•˜λ©΄ λΆˆν•„μš”ν•œ μ˜ˆμ™Έλ₯Ό λ°©μ§€ν•˜κ³ , 더 μ•ˆμ •μ μΈ μ½”λ“œλ₯Ό μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

     

    λ‹€μŒ κΈ€μ—μ„œλŠ” μ œλ„€λ¦­κ³Ό μ™€μΌλ“œμΉ΄λ“œλ‘œ νƒ€μž… μ•ˆμ •μ„± λ†’μ΄λŠ” 방법에 λŒ€ν•΄ λ‹€λ€„λ³΄κ² μŠ΅λ‹ˆλ‹€.

    κΆκΈˆν•œ 점이 μžˆλ‹€λ©΄ μ–Έμ œλ“  λŒ“κΈ€λ‘œ λ‚¨κ²¨μ£Όμ„Έμš” 😊

    λ°˜μ‘ν˜•