Является ли" бесконечный " итератор плохим дизайном?



это как правило, считается плохой практикой, чтобы обеспечить Iterator реализации, которые являются "бесконечными"; т. е. где вызовы hasNext() всегда(*) возвращает true?



обычно я бы сказал "Да", потому что вызывающий код может вести себя хаотично, но в реализации ниже hasNext() вернет true, если вызывающий объект не удалит все элементы из списка, с которым был инициализирован итератор; т. е. есть условие прекращения. Как вы думаете, это законно использование Iterator? Это не похоже на нарушение контракта, хотя я полагаю, что можно утверждать, что это неинтуитивно.



public class CyclicIterator<T> implements Iterator<T> {
private final List<T> l;
private Iterator<T> it;

public CyclicIterator<T>(List<T> l) {
this.l = l;
this.it = l.iterator();
}

public boolean hasNext() {
return !l.isEmpty();
}

public T next() {
T ret;

if (!hasNext()) {
throw new NoSuchElementException();
} else if (it.hasNext()) {
ret = it.next();
} else {
it = l.iterator();
ret = it.next();
}

return ret;
}

public void remove() {
it.remove();
}
}


(педантично) EDIT



некоторые люди прокомментировали, как Iterator может использоваться для генерации значений из неограниченной последовательности, такой как последовательность Фибоначчи. Однако, Java Iterator документация утверждает, что итератор:




итератор по коллекции.




теперь вы можно утверждать, что последовательность Фибоначчи является бесконечной коллекцией, но в Java я бы приравнял коллекцию к java.util.Collection интерфейс, который предлагает такие методы, как size() подразумевая, что коллекция должна быть ограничена. Поэтому, законно ли использовать Iterator как генератор значений из неограниченной последовательности?

727   11  

11 ответов:

Я думаю, что это совершенно законно - an Iterator это просто поток "вещей". Почему поток обязательно должен быть ограничен?

многие другие языки (например, Scala) имеют концепцию неограниченных потоков, встроенных в них, и они могут быть повторены. Например, с помощью scalaz

scala> val fibs = (0, 1).iterate[Stream](t2 => t2._2 -> (t2._1 + t2._2)).map(_._1).iterator
fibs: Iterator[Int] = non-empty iterator

scala> fibs.take(10).mkString(", ") //first 10 fibonnacci numbers
res0: String = 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

EDIT:С точки зрения принципа наименьшего удивления, я думаю, это полностью зависит от контекста. Например, чего бы я ожидал этот способ вернуть?

public Iterator<Integer> fibonacciSequence();

The все дело на Iterator Это то, что он ленив, т. е. что он дает вам только столько объектов, сколько вы просите. Если пользователь запрашивает все объекты бесконечного Iterator, Это их проблема, не твоя.

хотя я тоже думаю, что это законно, я хотел бы добавить, что такое Iterator (или точнее: an Iterable производить такой Iterator) не будет хорошо играть с расширенной for-loop (a. k. a для каждого цикла):

for (Object o : myIterable) {
   ...
}

поскольку код внутри расширенного цикла for не имеет прямого доступа к итератору, он не может вызвать remove() на итератор.

поэтому, чтобы закончить цикл, он должен был бы сделать одно из следующих действий:

  • получите доступ к внутренний List на Iterator и удалить объекты непосредственно, возможно, провоцируя ConcurrentModificationException
  • использовать break выход из цикла
  • " use " an Exception выход из цикла
  • использовать return чтобы выйти из цикла

все, кроме последней из этих альтернатив не лучший способ оставить расширенный цикл for.

"нормальный" for-loop (или любой другой цикл вместе с явным Iterator переменная) будет работать просто хорошо, конечно:

for (Iterator it = getIterator(); it.hasNext();) {
  Object o = it.next()
  ...
}

Это может быть семантика, но итератор должен старательно возвращать следующий элемент без учета, когда он закончится. Окончание является побочным эффектом для коллекции, хотя это может показаться общим побочным эффектом.

тем не менее, в чем разница между infinite и коллекцией с 10 триллионами предметов? Абонент хочет их всех, или его нет. Пусть абонент решает, сколько товаров можно вернуть или, когда до конца.

Я бы не сказал, что абонент не может использовать for-each строить. Он мог бы, пока ему нужны все предметы.

что-то в документации для коллекции, например "может быть бесконечной коллекцией", было бы уместно, но не "бесконечный итератор".

Это совершенно законное использование - до тех пор, пока это правильно документировано.

используя имя CyclicIterator Это хорошая идея, поскольку она предполагает, что цикл на итераторе вполне может быть бесконечным, если случай выхода из цикла не определен должным образом.

бесконечный итератор очень полезен при создании бесконечных данных, например, линейных рекуррентных последовательностей, таких как последовательность Фибоначчи.

Так что это совершенно нормально использовать такие.

да. Вам просто нужно разные критерии для остановки итерации.

концепция бесконечных списков очень распространена в ленивых языках оценки, таких как Haskell, и приводит к совершенно разным проектам программ.

на самом деле, an Iterator из Iterable,не a Collection, что бы ни говорил API JavaDoc. Последний расширяет первый, и именно поэтому он может генерировать Iterator также, но это совершенно разумно для Iterable не быть Collection. На самом деле, есть даже несколько классов Java, которые реализуют Iterable а не Collection.

теперь, что касается ожиданий... конечно, бесконечный итератор не должен быть доступен без четкого указания на то, что так уж случилось. Но иногда ожидания могут быть обманчивыми. Например, можно было бы ожидать InputStream чтобы всегда подходить к концу, но это действительно не является его требованием. Аналогично,Iterator возврат строк, прочитанных из такого потока, может никогда не закончиться.

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

с другой стороны, Python имеет генераторы, которые do расторгнуть.

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

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

если у вас есть объект, который нуждается в итераторе, и вы случайно даете ему бесконечный итератор, то все должно быть хорошо. Конечно, этот объект никогда не должен требуются iterator, чтобы остановить. И это - это потенциально плохой дизайн.

подумайте о музыкальном проигрывателе. Вы говорите ему, чтобы начать воспроизведение треков в "непрерывном режиме воспроизведения", и он воспроизводит треки, пока вы не скажете ему остановиться. Возможно, вы реализуете этот режим непрерывного воспроизведения, перетасовывая плейлист навсегда... пока пользователь не нажмет "стоп". В этом случае MusicPlayer должен итератор над плейлистом, что может быть Collection<Track>,навсегда. В этом случае необходимо либо CyclicIterator<Track> Как вы построили или ShuffleIterator<Track> что случайно выбирает следующий трек и никогда не заканчивается. Пока все идет хорошо.

MusicPlayer.play(Iterator<Track> playlist) было бы все равно, заканчивается ли список воспроизведения или нет. Он работает как с итератором по списку треков (воспроизведение до конца, затем остановка), так и с ShuffleIterator<Track> список треков (выберите случайный трек навсегда). Все еще хорошо.

что если кто-то пытался писать MusicPlayer.playContinuously()--который ожидает воспроизведения треков " навсегда "(пока кто-то не нажмет" стоп"), но принимает Iterator<Track> как параметр? это было бы проблемой дизайна. Ясно,playContinuously() нужен бесконечный Iterator, и если он принимает равнину Iterator как его параметр, то это был бы плохой дизайн. Это было бы, например, нужно проверить hasNext() и обрабатывать случай, когда это возвращает false, хотя это никогда не должно произойти. Плохой.

чтобы реализовать все бесконечные итераторы, которые вы хотите, пока их клиенты не зависит на итераторы конца. Как будто ваш клиент зависит от итератора, продолжающегося вечно, то не давайте ему равнину Iterator.

это должно быть очевидно, но это не кажется.

Comments

    Ничего не найдено.