Перейти к основному содержимому

Паттерн "Итератор" в Java — Iterable, пагинация и for-each

Разработчику Архитектору

Обзор GoF — в поведенческих паттернах. Аналог на C# (yield, LINQ) — в Итератор в C#. Здесь — Java: Iterable / Iterator, enhanced for и итератор, который скрывает пагинацию из БД.

Загрузка редактора схем…

Задача паттерна

Iterator даёт последовательный обход элементов без раскрытия внутреннего устройства коллекции (массив, дерево, курсор в БД). Клиент работает через "окно" hasNext() / next(), не зная, что за ним.

В Java контракт зафиксирован в JDK:

ТипРоль
Iterable<T>Коллекция умеет выдать итератор (iterator())
Iterator<T>hasNext(), next(), remove() (опционально)
for (T x : coll)Синтаксический сахар над Iterable

Пагинация из БД — свой Iterator

Клиент обходит пользователей как обычную последовательность; подгрузка страниц спрятана внутри итератора.

import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.function.Function;

public class PaginatedIterator<T> implements Iterator<T> {
private final Function<Integer, List<T>> pageFetcher;
private final int pageSize;
private List<T> currentPage;
private int pageIndex = 0;
private int itemIndex = 0;

public PaginatedIterator(Function<Integer, List<T>> pageFetcher, int pageSize) {
this.pageFetcher = pageFetcher;
this.pageSize = pageSize;
this.currentPage = pageFetcher.apply(0);
}

@Override
public boolean hasNext() {
if (itemIndex < currentPage.size()) {
return true;
}
if (currentPage.size() < pageSize) {
return false;
}
pageIndex++;
currentPage = pageFetcher.apply(pageIndex);
itemIndex = 0;
return !currentPage.isEmpty();
}

@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return currentPage.get(itemIndex++);
}
}

Использование (Spring Data в лямбде — пример):

Iterator<User> users = new PaginatedIterator<>(
page -> userRepository.findAll(PageRequest.of(page, 50)).getContent(),
50
);

while (users.hasNext()) {
process(users.next());
}

Тот же сценарий можно выразить через Stream и Spliterator, но явный Iterator нагляден и совместим с любым кодом, ожидающим Iterable.


Когда применять

СитуацияЗачем Iterator
Разные структуры, один способ обходаЕдиный контракт для списка, дерева, БД
Сложный обход (пагинация, ленивая подгрузка)Детали не утекают в клиент
Несколько независимых проходовУ каждого вызова iterator() своё состояние

Риски

Для простого ArrayList отдельный класс итератора избыточен — достаточно встроенного API и for-each.

При изменении коллекции во время обхода (кроме Iterator.remove()) JVM бросает ConcurrentModificationException — fail-fast защита от "битого" состояния.

Iterator и Stream

Для одноразового ленивого пайплайна чаще уместен Stream (Stream API). Собственный Iterator оправдан, когда нужна совместимость с legacy-кодом, точный контроль страниц или обход вне стандартных коллекций.


См. также

См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").