Итераторы. Iterator и Iterable

Последнее обновление: 13.10.2025

Одним из ключевых интерфейсов коллекций в языке Java является интерфейс Iterator. Он позволяет проходить по элементам коллекции.

Интерфейс Iterator имеет следующее определение:

public interface Iterator <E>{
    
   boolean hasNext();
   E next();
   default void remove();
   default void forEachRemaining(Consumer<? super E> action);
}

Реализация интерфейса предполагает, что с помощью вызова метода next() можно получить следующий элемент. С помощью метода hasNext() можно узнать, есть ли следующий элемент, и не достигнут ли конец коллекции. И если элементы еще имеются, то hasNext() вернет значение true. Метод hasNext() следует вызывать перед методом next(), так как при достижении конца коллекции метод next() выбрасывает исключение NoSuchElementException. И метод remove() удаляет текущий элемент, который был получен последним вызовом next().

Например, используем итератор для перебора коллекции ArrayList:

import java.util.Collection;
import java.util.ArrayList;
import java.util.Iterator;
 
public class Program{
      
    public static void main(String[] args) {
          
        Collection<String> people = new ArrayList<String>();
        people.add("Tom");
        people.add("Bob");
        people.add("Sam");
         
        // получаем интератор коллекции people
        Iterator<String> iter = people.iterator();
        // используя итератор, перебираем коллекцию
        while(iter.hasNext()){
         
            System.out.println(iter.next());
        }
    }
}

Консольный вывод:

Tom
Bob
Sam

Конечно, для перебора коллекции гораздо проще использовать стандартный цикл for each:

import java.util.Collection;
import java.util.ArrayList;
 
public class Program{
      
    public static void main(String[] args) {
          
        Collection<String> people = new ArrayList<String>();
        people.add("Tom");
        people.add("Bob");
        people.add("Sam");
         
        for(var p : people){
            System.out.println(p);
        }
    }
}

Однако "под капотом" компилятор преобразует цикл for-each в цикл с итератором.

Встроенные реализации итераторов

Интерфейс Iterator предоставляет ограниченный функционал. Но Java имеет встроенные типы, которые расширяют его функционал и предназначены для перебора определенных типов коллекций. Например, интерфейс ListIterator используется классами, которые реализуют интерфейс List и представляют списки, то есть классами LinkedList, ArrayList и др.

Интерфейс ListIterator расширяет интерфейс Iterator и определяет ряд дополнительных методов:

  • void add(E obj): вставляет объект obj перед элементом, который должен быть возвращен следующим вызовом next()

  • boolean hasNext(): возвращает true, если в коллекции имеется следующий элемент, иначе возвращает false

  • boolean hasPrevious(): возвращает true, если в коллекции имеется предыдущий элемент, иначе возвращает false

  • E next(): возвращает текущий элемент и переходит к следующему, если такого нет, то генерируется исключение NoSuchElementException

  • E previous(): возвращает текущий элемент и переходит к предыдущему, если такого нет, то генерируется исключение NoSuchElementException

  • int nextIndex(): возвращает индекс следующего элемента. Если такого нет, то возвращается размер списка

  • int previousIndex(): возвращает индекс предыдущего элемента. Если такого нет, то возвращается число -1

  • void remove(): удаляет текущий элемент из списка. Таким образом, этот метод должен быть вызван после методов next() или previous(), иначе будет сгенерировано исключение IlligalStateException

  • void set(E obj): присваивает текущему элементу, выбранному вызовом методов next() или previous(), ссылку на объект obj

Используем ListIterator:

import java.util.ListIterator;
import java.util.ArrayList;
 
public class Program{
      
    public static void main(String[] args) {
          
        var people = new ArrayList<String>();
        people.add("Tom");
        people.add("Bob");
        people.add("Sam"); 
         
        ListIterator<String> listIter = people.listIterator();
         
        while(listIter.hasNext()){
         
            System.out.println(listIter.next());
        }
        // сейчас текущий элемент - Sam
        // изменим значение этого элемента
        listIter.set("Sammy");
        // пройдемся по элементам в обратном порядке
        while(listIter.hasPrevious()){
         
            System.out.println(listIter.previous());
        }  
    }
}

Консольный вывод программы:

Tom
Bob
Sam
Sammy
Bob
Tom

Интерфейс Iterable

Цикл for-each работает с любым объектом, который реализует интерфейс Iterable и реализует его метод iterator():

public interface Iterable<E>
{
    Iterator<E> iterator();
    ....
}

В частности, этот метод возвращает итератор - то есть объект, реализующий интерфейс Iterator. И с помощью этого итератора будет осуществляться перебор объекта класса, которые реализует интерфейс Iterable.

Интерфейс Collection, а через него и все остальные коллекции, как раз расширяет интерфейс Iterable и наследует метод Iterator<E> iterator().:

public interface Collection<E>
{
    Iterator<E> iterator();
    ................

Поэтому вы можете использовать цикл "for each" с любой коллекцией в Java Collections Framework.

Кастомная реализация итераторов

Хотя итераторы и интерфейс Iterable предназначены прежде всего для работы с коллекциями, но фактически мы можем их использовать с любыми классами, рассматривая класс как набор некоторых объектов. Например, реализуем интерфейсы Iterable и Iterator для перебора объекта кастомного класса:

import java.util.Iterator;
 
public class Program{
      
    public static void main(String[] args) {
          
        Person tom = new Person(22, "Tom", 41);
        for(var prop : tom){
            System.out.println(prop);
        }
    }
}

class PersonIterator implements Iterator<String>{

    int currentProp = 0;
    Person person;

    PersonIterator(Person person){ this.person = person; }

    public boolean hasNext(){ return currentProp < 3; }

    public String next(){
        return switch(currentProp++){

            case 0 -> String.valueOf(person.getId());
            case 1 -> person.getName();
            case 2 -> String.valueOf(person.getAge());
            default -> null;
        };
    }
}

class Person implements Iterable{

    private int id;
    private String name;
    private int age;

    int getId() { return id;}
    String getName() { return name;}
    int getAge() { return age;}

    private PersonIterator iter = null;


    Person(int id, String name, int age){

        this.id = id;
        this.age = age;
        this.name = name;
    }

    public PersonIterator iterator(){
        if(iter == null){
            iter = new PersonIterator(this);
        }
        return iter;
    }
}

В данном случае демонстрируется, как сделать собственный класс итерируемым, то есть дать возможность перебирать его в цикле "for-each". И для начала определяем класс, объект которого будет перебираться - класс Person, который описывает человека:

class Person implements Iterable{
    // ... поля и конструктор ...

    public PersonIterator iterator(){
        if(iter == null){
            iter = new PersonIterator(this);
        }
        return iter;
    }
}

Основные моменты:

  • implements Iterable: реализуя этот интерфейс, класс Person обязуется предоставить метод iterator(), который неявно вызывается циклом "for-each".

  • public PersonIterator iterator(): этот метод должен вернуть объект, который, в свою очередь, реализует интерфейс Iterator. В данном случае он возвращает экземпляр нашего кастомного итератора PersonIterator.

Класс PersonIterator содержит всю логику перебора свойств объекта Person:

class PersonIterator implements Iterator<String>{
    // ... поля и конструктор ...

    public boolean hasNext(){ return currentProp < 3; }

    public String next(){
        return switch(currentProp++){
            // ...
        };
    }
}

Основные моменты:

  • implements Iterator<String>: применяем интерфейс, который будет перебирать значения типа String

  • boolean hasNext(): этот метод вызывается перед каждой итерацией цикла "for-each". Он должен вернуть true, если есть следующий элемент для перебора, и false, если элементы закончились. Здесь логика проста: поскольку у Person три свойства, метод возвращает true, пока счетчик currentProp меньше 3.

  • String next(): этот метод возвращает следующий элемент. Он вызывается, если hasNext() вернул true. С помощью оператора switch он последовательно возвращает значения полей id, name и age в виде строки (String), увеличивая счетчик currentProp на единицу после каждого вызова.

Таким образом, код создает специальную конструкцию в виде PersonIterator о том, как пошагово перебрать свойства объекта Person, и связывает эту конструкцию с самим классом Person через интерфейс Iterable

И далее в методе main мы можем создать объект класса Person и перебрать его свойства:

public static void main(String[] args) {
      
    Person tom = new Person(22, "Tom", 41);
    for(var prop : tom){
        System.out.println(prop);
    }
}

Результат выполнения программы:

22
Tom
41

Метод forEach

Вместо цикла "for each" для перебора коллекции можно вызвать методы Collection.forEach или Iterator.forEachRemaining с лямбда-выражением, которое использует элемент. Лямбда-выражение применяется ко всем элементам коллекции или к оставшимся элементам, которые может обойти итератор:

coll.forEach(element -> действия с element);
iter.forEachRemaining(element -> действия с element);

Например:

import java.util.ArrayList;
 
public class Program{
      
    public static void main(String[] args) {
          
        var people = new ArrayList<String>();
        people.add("Tom");
        people.add("Bob");
        people.add("Sam"); 
         
        // p представляет каждый объект в people
        people.forEach(p -> System.out.println(p) );
    }
}
Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850
Morty Proxy This is a proxified and sanitized view of the page, visit original site.