Является ли" бесконечный " итератор плохим дизайном?
это как правило, считается плохой практикой, чтобы обеспечить 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 как генератор значений из неограниченной последовательности?
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, 34EDIT:С точки зрения принципа наименьшего удивления, я думаю, это полностью зависит от контекста. Например, чего бы я ожидал этот способ вернуть?
public Iterator<Integer> fibonacciSequence();
The все дело на
IteratorЭто то, что он ленив, т. е. что он дает вам только столько объектов, сколько вы просите. Если пользователь запрашивает все объекты бесконечногоIterator, Это их проблема, не твоя.
хотя я тоже думаю, что это законно, я хотел бы добавить, что такое
Iterator(или точнее: anIterableпроизводить такой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,не aCollection, что бы ни говорил 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