CodeGym /Kursy /JAVA 25 SELF /Iterable i Iterator: iterowanie kolekcji

Iterable i Iterator: iterowanie kolekcji

JAVA 25 SELF
Poziom 27 , Lekcja 1
Dostępny

1. Interfejs Iterable

W Javie niemal wszystkie kolekcje (z wyjątkiem Map) implementują interfejs Iterable. Oznacza to, że można po nich przechodzić sekwencyjnie — element po elemencie, bez zagłębiania się w szczegóły wewnętrznej struktury. Dla programisty wygląda to tak, jakby „kolekcja miała wbudowany sposób przejścia po wszystkich elementach”.

Interfejs Iterable definiuje dokładnie jedną metodę:

Iterator<E> iterator();

Metoda iterator() zwraca obiekt typu Iterator — „pomocnika”, który wie, jak przejść kolekcję krok po kroku. Dzięki temu działa znana pętla for-each:

for (ElementType e : collection) {
    // ...
}

— za kulisami kryje się właśnie ten Iterator. Dodatkowy plus: z jego pomocą można bezpiecznie usuwać elementy podczas przechodzenia metodą remove(). Jeśli próbować robić to zwykłym przebiegiem, łatwo złapać ConcurrentModificationException.

2. Interfejs Iterator

Iterator to „kurier”, który potrafi iść po kolekcji, nie pomijając elementów i zachowując jej kolejność iteracji.

Metoda Opis
boolean hasNext()
Czy są jeszcze elementy do przejścia?
E next()
Zwraca następny element i przechodzi do niego
void remove()
Usuwa bieżący element bezpiecznie, bez błędów

Przykład iteracji kolekcji za pomocą Iterator

import java.util.*;

public class IteratorDemo {
    public static void main(String[] args) {
        List<String> tasks = new ArrayList<>();
        tasks.add("Pogłaskać kota");
        tasks.add("Zrobić zadanie domowe");
        tasks.add("Obejrzeć serial");

        Iterator<String> it = tasks.iterator();
        while (it.hasNext()) {
            String task = it.next();
            System.out.println("Zadanie: " + task);
        }
    }
}

Co się tu dzieje?

  • Pobieramy iterator przez tasks.iterator().
  • Dopóki jest następny element (hasNext() zwraca true), pobieramy go przez next() i wypisujemy.
  • Iterator sam pilnuje kolejności przechodzenia — nie musisz wiedzieć, jak kolekcja przechowuje elementy wewnątrz.

3. Po co nam Iterator, skoro mamy pętle?

Za pomocą Iterator można iterować po dowolnej kolekcji, nawet bez indeksów (np. Set). To uniwersalny sposób, który nie zależy od konkretnego typu kolekcji.

Bezpieczne usuwanie elementów

Częste zadanie: przejść po kolekcji i usunąć niektóre elementy. Jeśli zrobić to pętlą for-each, można otrzymać błąd:

for (String task : tasks) {
    if (task.contains("kot")) {
        tasks.remove(task); // BUM! ConcurrentModificationException
    }
}

Dlaczego tak się dzieje? Kolekcja nie oczekuje, że jej struktura będzie zmieniana bezpośrednio podczas przechodzenia zainicjowanego przez iterator.

Właściwy sposób:

Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
    String task = it.next();
    if (task.contains("kot")) {
        it.remove(); // Wszystko przejdzie gładko!
    }
}

Dlaczego nie można po prostu używać indeksów?

Bo nie wszystkie kolekcje mają indeksy. Na przykład HashSet albo TreeSet nie mają pojęcia „piątego elementu”. Iterator działa zawsze — na tym polega jego siła.

4. Szczegóły dotyczące for-each

Ulepszona pętla for (for-each) pojawiła się już w Javie 5. W istocie to cukier składniowy, który pozwala iterować po elementach maksymalnie prosto:

for (String task : tasks) {
    System.out.println("Zadanie: " + task);
}

Pod maską kompilator wywołuje iterator(), sprawdza elementy przez hasNext() i pobiera je za pomocą next(). Czyta się to dosłownie jak zdanie w języku naturalnym: „dla każdego zadania z listy”.

Kiedy for-each się nie sprawdzi?

  • Musisz usuwać elementy podczas iteracji (for-each nie pozwala wywołać remove() bezpośrednio).
  • Potrzebny jest dostęp do indeksu, aby np. podmienić element na pozycji.
  • Pracujesz z Map — ma pary „klucz–wartość”, do iteracji potrzebna jest specyficzna logika.

5. Iterowanie po Map: sztuczki i niuanse

Interfejs Map nie implementuje Iterable bezpośrednio, ponieważ to zbiór par „klucz–wartość”. Niemniej jednak, Map udostępnia wygodne widoki do iteracji.

Iteracja po kluczach

Map<String, String> users = new HashMap<>();
users.put("vasya", "vasya@example.com");
users.put("petya", "petya@gmail.com");

for (String login : users.keySet()) {
    System.out.println("Login: " + login);
}

Iteracja po wartościach

for (String email : users.values()) {
    System.out.println("Email: " + email);
}

Iteracja po parach (klucz–wartość)

Najbardziej uniwersalny sposób — iteracja po entrySet():

for (Map.Entry<String, String> entry : users.entrySet()) {
    System.out.println("Login: " + entry.getKey() + ", Email: " + entry.getValue());
}

Ciekawostka: Entry to wewnętrzny interfejs Map z metodami getKey() i getValue(). Dzięki temu od razu otrzymujesz obie części pary.

Iteracja przez Iterator

Iterator<Map.Entry<String, String>> it = users.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry<String, String> entry = it.next();
    // Można nawet bezpiecznie usunąć element:
    if (entry.getKey().startsWith("v")) {
        it.remove();
    }
}

6. Przykłady z życia: jak iterowanie kolekcji pomaga w aplikacji

Przykład: wypisujemy wszystkie zadania użytkownika

List<String> tasks = new ArrayList<>();
tasks.add("Zrobić zadanie domowe");
tasks.add("Pogłaskać kota");
tasks.add("Obejrzeć serial");

System.out.println("Twoje zadania na dziś:");
for (String task : tasks) {
    System.out.println("- " + task);
}

Teraz usuńmy wszystkie zadania zawierające słowo "kot":

Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
    String task = it.next();
    if (task.contains("kot")) {
        it.remove();
    }
}
System.out.println("Pozostały zadania:");
for (String task : tasks) {
    System.out.println("- " + task);
}

Przykład: iteracja unikalnych loginów przez Set

Set<String> logins = new HashSet<>();
logins.add("vasya");
logins.add("petya");
logins.add("masha");

for (String login : logins) {
    System.out.println("Użytkownik: " + login);
}

Zwróć uwagę: kolejność wypisywania w Set może być dowolna!

Przykład: iteracja Map w celu wyświetlenia użytkowników

Map<String, String> users = new HashMap<>();
users.put("vasya", "vasya@example.com");
users.put("petya", "petya@gmail.com");

for (Map.Entry<String, String> entry : users.entrySet()) {
    System.out.println("Login: " + entry.getKey() + ", Email: " + entry.getValue());
}

7. Iterator.remove(): bezpieczne usuwanie elementów

Jeden z najczęstszych błędów początkujących — próba usuwania elementów kolekcji podczas iteracji pętlą for-each. Iterator rozwiązuje ten problem za pomocą remove().

Jak to działa?

  • Po wywołaniu it.remove() usuwany jest bieżący element — ten, który zwrócił ostatni next().
  • To bezpieczne: kolekcja nie wyrzuca ConcurrentModificationException.

Przykład:

List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
Iterator<Integer> it = numbers.iterator();
while (it.hasNext()) {
    int n = it.next();
    if (n % 2 == 0) {
        it.remove(); // Usuwamy wszystkie liczby parzyste
    }
}
System.out.println(numbers); // [1, 3, 5]

Schemat iteracji kolekcji

+---------+     +---------+     +---------+
| Element | --> | Element | --> | Element | ...
+---------+     +---------+     +---------+
     ^               ^
     |               |
   next()         next()

Iterator „kroczy” po elementach, dopóki hasNext() nie zwróci false.

9. Typowe błędy przy pracy z Iterator i iteracją kolekcji

Błąd nr 1: modyfikacja kolekcji podczas iteracji pętlą for-each.
Próba usunięcia elementu bezpośrednio wewnątrz for-each prowadzi do ConcurrentModificationException:

for (String task : tasks) {
    if (task.contains("kot")) {
        tasks.remove(task); // BUM! ConcurrentModificationException
    }
}

Używaj Iterator i jego remove().

Błąd nr 2: wywołanie remove() przed next().
Najpierw trzeba pobrać bieżący element przez next(), w przeciwnym razie iterator nie wie, co usuwać.

Iterator<String> it = tasks.iterator();
it.remove(); // Błąd! Najpierw potrzebny jest next()

Błąd nr 3: próba iteracji po Map bezpośrednio w for-each.
Map nie implementuje Iterable bezpośrednio — używaj keySet(), values() albo entrySet().

Map<String, String> users = new HashMap<>();
// for (String entry : users) { ... } // Błąd: tak nie można
for (Map.Entry<String, String> e : users.entrySet()) {
    // poprawnie
}

Błąd nr 4: zmiana kolekcji poza iteratorem podczas przechodzenia iteratorem.
Podczas iteracji usuwaj elementy tylko przez it.remove(), a nie przez metody kolekcji.

Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
    String task = it.next();
    if (task.contains("kot")) {
        tasks.remove(task); // Błąd! Trzeba użyć it.remove()
    }
}
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION