0Pricing
Java Academy · Lekcja

Implementowanie własnego iteratora

Zbuduj własną klasę iteratora dla prostej listy wiązanej lub struktury reprezentującej zakres.

Implementowanie własnego iteratora to bezpłatna lekcja Java Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Java Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Java Academy zawiera 4 lekcji w sumie.

Własny iterator

Zbudowanie własnego iteratora daje pełną kontrolę nad sposobem przechodzenia po strukturze danych. W tej lekcji krok po kroku zaimplementujesz iterator listy wiązanej.

Klasa Node

Najpierw zdefiniuj strukturę węzła dla jednokierunkowej listy wiązanej.

class Node<T> {
    final T value;
    Node<T> next;

    Node(T value) {
        this.value = value;
    }
}

// Building a chain: 1 -> 2 -> 3
Node<Integer> head = new Node<>(1);
head.next = new Node<>(2);
head.next.next = new Node<>(3);

Implementowanie iteratora

Utwórz klasę wewnętrzną implementującą Iterator<T> z kursorem wskazującym bieżący węzeł.

import java.util.Iterator;
import java.util.NoSuchElementException;

class LinkedList<T> implements Iterable<T> {
    private Node<T> head;
    private int size;

    private class LinkedListIterator implements Iterator<T> {
        private Node<T> current = head; // cursor

        @Override
        public boolean hasNext() {
            return current != null;
        }

        @Override
        public T next() {
            if (!hasNext()) throw new NoSuchElementException();
            T value = current.value;
            current = current.next;
            return value;
        }
    }

    @Override
    public Iterator<T> iterator() {
        return new LinkedListIterator();
    }
}

addFirst i kompletna klasa LinkedList

Dodaj możliwość umieszczania węzłów na początku listy i zobacz kompletną, działającą klasę.

class LinkedList<T> implements Iterable<T> {
    private Node<T> head;
    private int size;

    public void addFirst(T value) {
        Node<T> node = new Node<>(value);
        node.next = head;
        head = node;
        size++;
    }

    public void addLast(T value) {
        Node<T> node = new Node<>(value);
        if (head == null) { head = node; }
        else {
            Node<T> curr = head;
            while (curr.next != null) curr = curr.next;
            curr.next = node;
        }
        size++;
    }

    public int size() { return size; }

    @Override
    public Iterator<T> iterator() {
        return new LinkedListIterator();
    }
}

Używanie własnego iteratora

Po zaimplementowaniu interfejsu Iterable lista wiązana działa w pętlach for-each oraz z metodą forEach.

LinkedList<String> list = new LinkedList<>();
list.addLast("Alice");
list.addLast("Bob");
list.addLast("Charlie");

// For-each loop works!
for (String name : list) {
    System.out.println(name);
}
// Alice
// Bob
// Charlie

// Stream also works (Java 8+)
list.forEach(name -> System.out.println("Hello, " + name));

Iterator zakresu

Prostszy przykład: iterator przechodzący po zakresie liczbowym bez bazowej struktury danych.

class IntRange implements Iterable<Integer> {
    private final int start, end, step;

    IntRange(int start, int end, int step) {
        this.start = start; this.end = end; this.step = step;
    }
    IntRange(int start, int end) { this(start, end, 1); }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<>() {
            int current = start;
            public boolean hasNext() { return current < end; }
            public Integer next() {
                if (!hasNext()) throw new NoSuchElementException();
                int val = current;
                current += step;
                return val;
            }
        };
    }
}

for (int n : new IntRange(0, 10, 2)) System.out.print(n + " ");
// 0 2 4 6 8

Iterator przejścia drzewa inorder

Implementacja iteratora przejścia drzewa BST w kolejności inorder z użyciem jawnego stosu — pokazuje, jak iteratory mogą zastąpić rekurencyjne przechodzenie.

import java.util.*;

class BinaryTree<T extends Comparable<T>> {
    private record TreeNode<T>(T val, TreeNode<T> left, TreeNode<T> right) {}

    private TreeNode<T> root;

    public Iterator<T> inorderIterator() {
        Deque<TreeNode<T>> stack = new ArrayDeque<>();
        pushLeft(root, stack);
        return new Iterator<>() {
            public boolean hasNext() { return !stack.isEmpty(); }
            public T next() {
                TreeNode<T> node = stack.pop();
                pushLeft(node.right(), stack);
                return node.val();
            }
        };
    }

    private void pushLeft(TreeNode<T> node, Deque<TreeNode<T>> stack) {
        while (node != null) { stack.push(node); node = node.left(); }
    }
}

Leniwy iterator

Iteratory mogą generować wartości leniwie — dopiero po wywołaniu next(). Jest to przydatne w przypadku nieskończonych sekwencji.

class FibonacciIterator implements Iterator<Long> {
    private long a = 0, b = 1;

    @Override public boolean hasNext() { return true; } // infinite!

    @Override public Long next() {
        long result = a;
        long next = a + b;
        a = b;
        b = next;
        return result;
    }
}

Iterator<Long> fib = new FibonacciIterator();
for (int i = 0; i < 10; i++) System.out.print(fib.next() + " ");
// 0 1 1 2 3 5 8 13 21 34

Iterator filtrujący

Iterator-dekorator opakowujący inny iterator i pomijający elementy, które nie spełniają predykatu.

import java.util.*;
import java.util.function.*;

class FilterIterator<T> implements Iterator<T> {
    private final Iterator<T> source;
    private final Predicate<T> predicate;
    private T next;
    private boolean hasNext;

    FilterIterator(Iterator<T> source, Predicate<T> predicate) {
        this.source = source; this.predicate = predicate;
        advance();
    }

    private void advance() {
        hasNext = false;
        while (source.hasNext()) {
            T candidate = source.next();
            if (predicate.test(candidate)) { next = candidate; hasNext = true; break; }
        }
    }

    public boolean hasNext() { return hasNext; }
    public T next() { T val = next; advance(); return val; }
}

List<Integer> nums = List.of(1,2,3,4,5,6,7,8,9,10);
Iterator<Integer> evens = new FilterIterator<>(nums.iterator(), n -> n % 2 == 0);
while (evens.hasNext()) System.out.print(evens.next() + " ");
// 2 4 6 8 10

Integracja iteratora ze strumieniem

Własne iteratory można przystosować do użycia ze strumieniami za pomocą Spliterators.spliteratorUnknownSize().

import java.util.*;
import java.util.stream.*;

Iterator<Integer> rangeIt = new IntRange(1, 6).iterator();

Stream<Integer> stream = StreamSupport.stream(
    Spliterators.spliteratorUnknownSize(rangeIt, Spliterator.ORDERED),
    false // not parallel
);

int sum = stream.mapToInt(Integer::intValue).sum();
System.out.println(sum); // 15

Usuwanie podczas iterowania

Opcjonalna metoda remove() interfejsu Iterator usuwa element zwrócony przez ostatnie wywołanie next() — w przypadku własnych iteratorów trzeba ją zaimplementować jawnie.

class MutableLinkedList<T> implements Iterable<T> {
    // ... (full implementation)

    // Iterator with remove support
    private class RemovableIterator implements Iterator<T> {
        private Node<T> prev = null;
        private Node<T> current = head;

        public boolean hasNext() { return current != null; }
        public T next() {
            prev = (prev == null) ? null : current;
            T val = current.value;
            current = current.next;
            return val;
        }

        public void remove() {
            // Remove the last returned node
            if (prev == null) head = current;
            else prev.next = current;
            size--;
        }
    }
}

Lista kontrolna iteratora

Podczas implementowania własnego iteratora:

  • Zawsze wywołuj hasNext() przed next()
  • Gdy iterator jest pusty, metoda next() powinna zgłaszać NoSuchElementException, a nie zwracać null
  • Nie przechowuj w iteratorze stanu zależnego od kolekcji (nie buforuj rozmiaru kolekcji)
  • W razie potrzeby użyj modCount do wykrywania równoczesnych modyfikacji

Szybkie sprawdzenie

Jaki wyjątek powinna zgłaszać metoda next(), gdy nie ma już więcej elementów?

Podsumowanie: implementowanie własnego iteratora

Najważniejsze informacje:

  • Implementuj Iterator za pomocą metod hasNext(), next() oraz opcjonalnej remove()
  • Przechowuj w iteratorze pole kursora wskazujące następny element
  • Gdy hasNext() zwraca false, metoda next() powinna zgłaszać NoSuchElementException
  • Twórz nową instancję iteratora przy każdym wywołaniu iterator(), aby uzyskać niezależne kursory
  • Leniwe iteratory generują wartości na żądanie — są przydatne w przypadku nieskończonych sekwencji
  • Opakowuj iteratory w StreamSupport.stream(), aby połączyć je z API Stream

Często zadawane pytania

Czy lekcja „Implementowanie własnego iteratora” jest bezpłatna?

Tak — pełny tekst „Implementowanie własnego iteratora” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Java Academy, przejdź na CoddyKit PRO. Kurs Java Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Implementowanie własnego iteratora”?

Zbuduj własną klasę iteratora dla prostej listy wiązanej lub struktury reprezentującej zakres. Ćwiczysz Java Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Java Academy?

Nie wymagamy żadnego doświadczenia. Java Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.

Ile czasu zajmuje lekcja „Implementowanie własnego iteratora”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Java Academy?

Tak. Każda lekcja Java Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Kontrakty Iterable i Iterator
  2. Implementowanie własnego iteratora
  3. ListIterator i przechodzenie dwukierunkowe
  4. Iteratory fail-fast a fail-safe
← Powrót do Java Academy