Iterator Pattern w kolekcjach – Jak elegancko przeglądać dane

TL;DR: Iterator Pattern to wzorzec projektowy pozwalający na jednolite przeglądanie różnych kolekcji bez ujawniania ich wewnętrznej struktury. W Javie każda kolekcja implementuje interfejs Iterable, dzięki czemu możesz używać pętli for-each lub ręcznie kontrolować iterację przez Iterator.

Wyobraź sobie, że masz pudełko z książkami, szufladę z płytami CD i regał z grami. Każde „pojemnik” ma inną strukturę, ale chcesz przeglądać ich zawartość w ten sam sposób. Iterator Pattern rozwiązuje dokładnie ten problem w programowaniu.

Iterator to jak przewodnik po muzeum – niezależnie od tego, czy zwiedzasz salę z obrazami, rzeźbami czy starożytnościami, przewodnik zawsze mówi „następny eksponat” i prowadzi Cię dalej.

## Dlaczego to ważne?

Iterator Pattern jest fundamentem dla pętli for-each w Javie i podstawą Java Collections Framework. Bez zrozumienia tego wzorca nie będziesz mógł efektywnie pracować z listami, setami czy mapami. To jeden z najczęściej używanych wzorców w codziennym programowaniu.

Co się nauczysz:

  • Czym jest Iterator Pattern i jak działa
  • Różnica między Iterator a pętlą for-each
  • Jak bezpiecznie usuwać elementy podczas iteracji
  • Praktyczne zastosowania w ArrayList, LinkedList i HashMap
  • Kiedy używać każdego podejścia
  • Najczęstsze błędy i jak ich uniknąć
Wymagania wstępne: Podstawy Java, znajomość klas ArrayList i LinkedList, rozumienie interfejsów w Javie.

## Czym jest Iterator Pattern?

Iterator Pattern to wzorzec behawioralny, który dostarcza sposób na sekwencyjne dostępowanie do elementów kolekcji bez ujawniania jej wewnętrznej reprezentacji. W Javie realizowany jest przez interfejs Iterator.

// Interfejs Iterator w Javie
public interface Iterator {
    boolean hasNext();    // Czy są jeszcze elementy?
    E next();            // Pobierz następny element
    void remove();       // Usuń bieżący element (opcjonalne)
}

// Interfejs Iterable - każda kolekcja go implementuje
public interface Iterable {
    Iterator iterator();  // Zwróć iterator dla tej kolekcji
}

## Iterator w praktyce – ArrayList

Zobaczmy jak Iterator działa z najpopularniejszą kolekcją – ArrayList:

import java.util.*;

public class IteratorExample {
    public static void main(String[] args) {
        // Tworzymy listę miast
        ArrayList miasta = new ArrayList<>();
        miasta.add("Warszawa");
        miasta.add("Kraków");
        miasta.add("Gdańsk");
        miasta.add("Wrocław");
        
        // Sposób 1: Iterator ręczny
        System.out.println("=== Iterator ręczny ===" );
        Iterator iterator = miasta.iterator();
        while (iterator.hasNext()) {
            String miasto = iterator.next();
            System.out.println("Miasto: " + miasto);
        }
        
        // Sposób 2: Pętla for-each (używa Iterator pod spodem)
        System.out.println("=== Pętla for-each ===" );
        for (String miasto : miasta) {
            System.out.println("Miasto: " + miasto);
        }
    }
}
Pętla for-each to syntactic sugar – kompilator automatycznie zamienia ją na Iterator. Oba podejścia działają identycznie pod spodem.

## Bezpieczne usuwanie elementów

Jedna z najważniejszych zalet Iterator to możliwość bezpiecznego usuwania elementów podczas przeglądania kolekcji:

import java.util.*;

public class SafeRemovalExample {
    public static void main(String[] args) {
        ArrayList liczby = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            liczby.add(i);
        }
        
        // ❌ BŁĄD - modyfikacja podczas for-each
        // To spowoduje ConcurrentModificationException!
        /*
        for (Integer liczba : liczby) {
            if (liczba % 2 == 0) {
                liczby.remove(liczba); // BŁĄD!
            }
        }
        */
        
        // ✅ POPRAWNIE - użyj Iterator.remove()
        Iterator iterator = liczby.iterator();
        while (iterator.hasNext()) {
            Integer liczba = iterator.next();
            if (liczba % 2 == 0) {
                iterator.remove(); // Bezpieczne usuwanie
                System.out.println("Usunięto: " + liczba);
            }
        }
        
        System.out.println("Pozostałe liczby: " + liczby);
        // Wynik: [1, 3, 5, 7, 9]
    }
}
Uwaga: Nigdy nie modyfikuj kolekcji bezpośrednio podczas iteracji pętlą for-each. Zawsze używaj Iterator.remove() do bezpiecznego usuwania.

## Iterator z różnymi kolekcjami

Iterator działa jednakowo niezależnie od typu kolekcji:

import java.util.*;

public class DifferentCollectionsExample {
    public static void main(String[] args) {
        // ArrayList - lista z dostępem przez indeks
        List arrayList = new ArrayList<>();
        arrayList.add("Java");
        arrayList.add("Python");
        arrayList.add("JavaScript");
        
        // LinkedList - lista dwukierunkowa
        List linkedList = new LinkedList<>();
        linkedList.add("Spring");
        linkedList.add("Hibernate");
        linkedList.add("Maven");
        
        // HashSet - zbiór unikalnych elementów
        Set hashSet = new HashSet<>();
        hashSet.add("HTML");
        hashSet.add("CSS");
        hashSet.add("Bootstrap");
        
        // Jedna metoda obsługuje wszystkie kolekcje!
        System.out.println("Języki programowania:");
        printCollection(arrayList);
        
        System.out.println("Technologie Java:");
        printCollection(linkedList);
        
        System.out.println("Technologie frontend:");
        printCollection(hashSet);
    }
    
    // Uniwersalna metoda - działa z każdą kolekcją
    public static void printCollection(Iterable collection) {
        for (String element : collection) {
            System.out.println("- " + element);
        }
    }
}

## Iterator z HashMap

HashMap nie implementuje Iterable bezpośrednio, ale można iterować po jej elementach:

import java.util.*;

public class HashMapIteratorExample {
    public static void main(String[] args) {
        Map oceny = new HashMap<>();
        oceny.put("Matematyka", 5);
        oceny.put("Fizyka", 4);
        oceny.put("Chemia", 3);
        oceny.put("Biologia", 5);
        
        // Sposób 1: Iteracja po kluczach
        System.out.println("=== Iteracja po kluczach ===" );
        for (String przedmiot : oceny.keySet()) {
            System.out.println(przedmiot + ": " + oceny.get(przedmiot));
        }
        
        // Sposób 2: Iteracja po wartościach
        System.out.println("=== Iteracja po wartościach ===" );
        for (Integer ocena : oceny.values()) {
            System.out.println("Ocena: " + ocena);
        }
        
        // Sposób 3: Iteracja po parach klucz-wartość (najefektywniej)
        System.out.println("=== Iteracja po Entry ===" );
        for (Map.Entry entry : oceny.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }
}
Pro tip: Gdy potrzebujesz zarówno klucza jak i wartości z HashMap, używaj entrySet(). To najefektywniejszy sposób – unikasz dodatkowych wywołań get().

## Kiedy używać Iterator vs for-each?

SytuacjaUżyjDlaczego
Przeglądanie bez modyfikacjifor-eachCzytelniejszy kod
Usuwanie elementówIteratorBezpieczne usuwanie
Kontrola nad iteracjąIteratorMożliwość zatrzymania/wznowienia
Zagnieżdżone pętlefor-eachLepszy performance
Przetwarzanie warunkoweIteratorWiększa kontrola

## Najczęstsze błędy

Typowy błąd: Wywoływanie next() bez sprawdzenia hasNext() – prowadzi do NoSuchElementException.
// ❌ BŁĄD - może wywołać wyjątek
Iterator iterator = lista.iterator();
String pierwszy = iterator.next(); // Co jeśli lista jest pusta?

// ✅ POPRAWNIE - zawsze sprawdzaj hasNext()
Iterator iterator = lista.iterator();
if (iterator.hasNext()) {
    String pierwszy = iterator.next();
    System.out.println(pierwszy);
} else {
    System.out.println("Lista jest pusta");
}
Pułapka: Modyfikowanie kolekcji podczas iteracji for-each powoduje ConcurrentModificationException – nawet w single-threaded aplikacji!
Czy for-each jest szybszy od Iterator?

Nie, for-each używa Iterator pod spodem. Performance jest identyczny, ale for-each ma czytelniejszą składnię.

Czy mogę używać kilku Iterator na tej samej kolekcji?

Tak, każde wywołanie iterator() tworzy nowy, niezależny Iterator. Każdy ma własny stan iteracji.

Co się stanie jeśli zapomnę o hasNext()?

Jeśli wywołasz next() gdy nie ma więcej elementów, dostaniesz NoSuchElementException. Zawsze sprawdzaj hasNext() przed next().

Czy Iterator działa z tablicami (array)?

Zwykłe tablice nie implementują Iterable, ale for-each działa dzięki specjalnej obsłudze w kompilerze. Do tablic używaj for-each lub tradycyjnej pętli for.

Kiedy użyć ListIterator zamiast Iterator?

ListIterator pozwala na iterację w obu kierunkach i modyfikację elementów. Używaj go z List gdy potrzebujesz więcej możliwości niż podstawowy Iterator.

## Przydatne zasoby

– [Oracle Java Iterator Documentation](https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html)
– [Java Collections Framework Guide](https://docs.oracle.com/javase/tutorial/collections/)
– [Effective Java – Best Practices](https://www.oracle.com/technetwork/java/effectivejava-136174.html)

🚀 Zadanie dla Ciebie

Stwórz program, który:

  1. Wczytuje listę słów od użytkownika
  2. Używa Iterator do usunięcia wszystkich słów krótszych niż 5 znaków
  3. Wyświetla pozostałe słowa używając pętli for-each
  4. Zmierz czas wykonania dla 1000 słów

Bonus: Porównaj wydajność Iterator.remove() vs tworzenie nowej listy z elementami spełniającymi warunek.

Czy używasz Iterator w swoich projektach? Podziel się w komentarzach swoimi doświadczeniami z przeglądaniem kolekcji!

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Przewijanie do góry