Как работать с Iterable

Допустим нам захотелось сделать класс Арифметической Прогрессии (Arithmetic Sequence, скрщн AS) которой можно было бы использовать вот так

public class Main {
    public static void main(String[] args) {
        AS seq = new AS();
        for(int el : seq) {
            System.out.println(el);
        }
    }
}

и чтобы оно выдавало

[0, 1, 2, 3, 4, 5, 6, 7, 8...]

к сожалению напрямую такой подход работать не будет

ну и понятно почему, с чего джава должна знать, что мы имеем ввиду если хотим итерировать по какому-то объекту.

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

Их не надо вручную писать, они уже встроены в дажву, и устроены примерно так:

// интерфейс с дженериком E, 
// E определяет какого-типа объекты должна возвращать последовательность
public interface Iterator<E> {
    // эта функция используется чтобы понять надо ли продолжать процесс итерирования
    boolean hasNext();
    
    // а эта функция должна возвращать очередной элемент последовательности
    E next();
}

и второй, скорее промежуточный интерфейс, который должен вернуть экземпляр предыдущего интерфейса

public interface Iterable<T> {
    // собственно эта функция и должна вернуть итератор
    Iterator<T> iterator();
}

И так, работаем с ними по следующей схеме.

Сначала опишем итератор, создадим класс

// наследуем от Iterator причем указываем тип дженерика <Integer>, 
// так как будем генерировать последовательность целых чисел 
public class ASIterator implements Iterator<Integer> {
    
}

чтобы быстро создать все методы для переопределения в IDEA, можно поставить курсор на имя класса и тыкнуть правой кнопкой:

получилось что-то в этом роде:

public class ASIterator implements Iterator<Integer> {
    @Override
    public boolean hasNext() {
        return false;
    }

    @Override
    public Integer next() {
        return null;
    }
}

и так сначала подкрутим метод hasNext, он должен возвращать true или false в зависимости от того есть следующий элемент или нет.

Если мы сразу возвращаем false это значит, что наша последовательность не будет иметь элементов, а если мы поставим туда true она будет бесконечной.

Давайте для начала поставим туда true, а затем уже запихаем туда какое-нибудь условие, чтобы ограничить количество элементов.

@Override
public boolean hasNext() {
    return true; // поменял на true
}

теперь надо подкрутить next(), этот метод должен выдавать очередной элемент последовательности.

Чтобы что-то считать, надо добавить какой-то счетчик который будет возрастать на единицу каждую итерацию

public class ASIterator implements Iterator<Integer> {
    int counter = 0; // добавил счетчик

    @Override
    public boolean hasNext() { /* ... */ }
    @Override
    public Integer next() { /* ... */ }
}

теперь сделаем чтобы наш метод next выдавал текущее значение счетчика и увеличивал его на 1

public class ASIterator implements Iterator<Integer> {
    /* ... */

    @Override
    public Integer next() {
        int value = counter; // фиксируем предыдущее значение counter в value
        counter += 1; // увеличили счетчик
        return value; // вернули предыдущее значение
    }
}

теперь подключим этот итератор к нашему классу AS. Делается это очень просто.

Сначала наследуем наш класс от интерфейса Iterable:

public class AS implements Iterable<Integer> {
    
}

добавим в него метод iterator

public class AS implements Iterable<Integer> {
    @Override
    public Iterator<Integer> iterator() {
        return null;
    }
}

теперь просто сделаем чтобы этот метод возвращал экземпляр класса итератора

public class AS implements Iterable<Integer> {
    @Override
    public Iterator<Integer> iterator() {
        return new ASIterator();
    }
}

собственно все, теперь можно попробовать запустить:

Ура! =)

Тут в принципе все прекрасно, только последовательность у нас бесконечно считает. Как я уже говорил выше, это из-за того, что функция hasNext выдает всегда true.

Давайте добавим поле к нашей последовательность в котором будет указан максимальный возможный элемент:

public class AS implements Iterable<Integer> {
    int iMax; // добавил поле

    public AS(int iMax) { // добавил конструктор, чтобы пробрасывать через него значение в поле
        this.iMax = iMax;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new ASIterator();
    }
}

теперь нужно создавать вот так нашу последовательность:


public class Main {
    public static void main(String[] args) {
        AS seq = new AS(10); // тут теперь указываю параметр
        for(int el : seq) {
            System.out.println(el);
        }
    }
}

правда если это запустить, то все равно пока будет бесконечное количество элементов:

так как за генерацию отвечает итератор надо пробросить значение туда. Для этого добавим в итератор такое же поле и конструктор

public class ASIterator implements Iterator<Integer> {
    int counter = 0;
    int iMax; // добавил поле

    public ASIterator(int iMax) {
        this.iMax = iMax; // добавил конструктор
    }

    @Override
    public boolean hasNext() { /* ... */ }
    @Override
    public Integer next() { /* ... */ }
}

теперь подправим класс последовательности

public class AS implements Iterable<Integer> {
    int iMax = 10;

    public AS(int iMax) {
        this.iMax = iMax;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new ASIterator(iMax); // пробрасываю сюда iMax
    }
}

пробую запустить:

черт, все равно не работает. А! Ну да, мы же условие hasNext так и не подправили.

Сделаем чтобы в hasNext в итераторе учитывалось, что counter не превысил iMax

public class ASIterator implements Iterator<Integer> {
    // ...

    @Override
    public boolean hasNext() {
        return counter < iMax; // вместо true, буду проверять условие counter < iMax
    }

    @Override
    public Integer next() { /* ... */ }
}

так как каждая очередная итерация вызывает функцию next, которая увеличивает counter. То получим механизм который остановится когда счетчик достигнет iMax

Проверяем:

Упрощаем код

Так как итератор напрямую связан с последовательностью и по сути iMax последовательности и iMax итератора это одно и тоже, то чтобы не дублировать лишние поля и конструкторы, воспользуемся вложенным классом и запихаем класс итератора прямо в класс последовательности

public class AS implements Iterable<Integer> {
    int iMax;

    public AS(int iMax) {
        this.iMax = iMax;
    }

    public class ASIterator implements Iterator<Integer> {
        int counter = 0;
        /*
        это теперь не нужно
        int iMax;

        public ASIterator(int iMax) {
            this.iMax = iMax;
        }
       */

        @Override
        public boolean hasNext() {
            return counter < iMax; // iMax подхватывается теперь из класса AS
        }

        @Override
        public Integer next() {
            int value = counter;
            counter += 1;
            return value;
        }
    }

    @Override
    public Iterator<Integer> iterator() {
        return new ASIterator(); // тут убрали iMax
    }
}

итого получим вот такой код:

public class AS implements Iterable<Integer> {
    int iMax;

    public AS(int iMax) {
        this.iMax = iMax;
    }

    public class ASIterator implements Iterator<Integer> {
        int counter = 0;

        @Override
        public boolean hasNext() {
            return counter < iMax;
        }

        @Override
        public Integer next() {
            int value = counter;
            counter += 1;
            return value;
        }
    }

    @Override
    public Iterator<Integer> iterator() {
        return new ASIterator();
    }
}

По сути рассматривайте описание класса как некий эквивалент цикла. Где, то что в next, это тело цикла, то что в hasNext это условие при котором цикл работает. Ну типа так

Попробуйте написать класс, который будет считать геометрическую прогрессию.

Если у вас что-то не работает, или какой-то момент не понятен, или не получается написать класс для расчета геометрической прогрессии, то напишите мне в вк я с удовольствием вас проконсультирую.