Как работать с 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 это условие при котором цикл работает. Ну типа так
Попробуйте написать класс, который будет считать геометрическую прогрессию.
Если у вас что-то не работает, или какой-то момент не понятен, или не получается написать класс для расчета геометрической прогрессии, то напишите мне в вк я с удовольствием вас проконсультирую.