Идиоматический способ использования для каждого цикла с учетом итератора?

Когда расширенный цикл for (цикл foreach) был добавлен в Java, он был настроен для работы с целевым объектом массива илиIterable.

for ( T item : /*T[] or Iterable<? extends T>*/ ) {
    //use item
}

Это прекрасно работает для классов Collection, которые реализуют только один тип итерации и, таким образом, имеют одинiterator() метод.

Но я нахожусь невероятно расстроенным из-за того, что я хочу использовать нестандартный итератор из класса Collection. Например, я недавно пытался помочь кому-то использоватьDeque как LIFO / стек, но затем печатать элементы в порядке FIFO. Я был вынужден сделать это:

for (Iterator<T> it = myDeque.descendingIterator(); it.hasNext(); ) {
   T item = it.next();
   //use item
}

Я теряю преимущества для каждого цикла. Это не просто нажатия клавиш. Мне не нравится выставлять итератор, если мне не нужно, так как легко ошибиться, вызвавit.next() дважды и т. д.

Теперь в идеале я думаю, что цикл для каждого должен был принятьIterator также. Но это не так. Так есть ли идиоматический способ использования цикла for-each в этих обстоятельствах? Я также хотел бы услышать предложения, которые используют библиотеки общих коллекций, такие как Guava.

Лучшее, что я могу придумать в отсутствие вспомогательного метода / класса:

for ( T item : new Iterable<T>() { public Iterator<T> iterator() { return myDeque.descendingIterator(); } } ) {
    //use item
}

Который не стоит использовать.

Я бы хотел увидеть что-то подобное в ГуавеIterables.wrap чтобы сделать это идиоматическим, но ничего подобного не нашел. Очевидно, что я мог бы свернуть свою собственную обертку Iterator через метод класса или вспомогательный метод. Есть другие идеи?

Редактировать: Как примечание, может ли кто-нибудь дать вескую причину того, почему расширенный цикл for не мог просто принятьIterator? Это, вероятно, во многом помогло бы мне жить с нынешним дизайном.

 Mark Peters07 окт. 2010 г., 22:23
О, @Kevin, я полагаю, ты можешь просто процитировать себя. Профиль прочитан.
 Kevin Bourrillion07 окт. 2010 г., 22:14
примечание: Guava выступает против любой утилиты, чтобы рассматривать итератор как итерируемый. Возвращаемое Iterable было бы случайностью, ожидающей того, что произойдет, и зацикливание на нем не сделает ваш код немного короче.
 Mark Peters07 окт. 2010 г., 22:29
Для справки, я нашел эту идею на «кладбище идей» в Гуаве:code.google.com/p/guava-libraries/wiki/IdeaGraveyard.
 thSoft05 февр. 2013 г., 15:46
@ dimo414 ИМХО, вы действительно должны преобразовать свой комментарий в ответ! Я думаю, что это сжато и достаточно явно.
 Mark Peters07 окт. 2010 г., 22:17
@Kevin: можете ли вы привести пример этого (комментарии к запросу или что-то в этом роде?)
 dimo41422 нояб. 2012 г., 18:56
Для ленивых пользователи гуавы могут сделатьImmutableList.copyOf(Iterator) безопасно преобразовать итератор в итерируемое. Смотрите выше ссылку для более подробной информации о том, почему.

Ответы на вопрос(8)

public class DescendingIterableDequeAdapter<T> implements Iterable<T> {
    private Deque<T> original;

    public DescendingIterableDequeAdapter(Deque<T> original) {
        this.original = original;
    }

    public Iterator<T> iterator() {
         return original.descendingIterator();
    }
}

А потом

for (T item : new DescendingIterableDequeAdapter(deque)) {

}

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

Что касается вашего дополнительного вопроса - я полагаю, потому что цикл for-each действительно должен был сделать вещи короче для сценариев общего назначения. А вызов дополнительного метода делает синтаксис более многословным. Это могло бы поддержать какIterable а такжеIterator, но что, если переданный объект реализован как? (было бы странно, но все же возможно).

 ColinD07 окт. 2010 г., 17:56
@Mark Peters: Это очень просто сделать, поэтому я бы порекомендовал просто сделать метод, чтобы сделать это самостоятельно. Я не думаю, что какая-либо библиотека, по крайней мере, не такая качественная, как Guava, могла бы использовать такую ​​функцию, которая поощряет людей совершать потенциально плохие поступки.
 Bozho07 окт. 2010 г., 17:45
@ Марк Питерс - такого не может быть. В лучшем случае он скрыл бы все эти конкретные адаптеры за статическим утилитарным методом, который использовал бы хотя бы некоторое отражение или имел бы перечисление для выбора желаемого типа.
 Mark Peters07 окт. 2010 г., 17:43
Определенно работает, но вряд ли идиоматично, если мне нужно написать новый класс для каждого типа итератора, для которого я хочу его использовать. Я надеялся на что-то, что сработало бы с произвольным итератором.
 Mark Peters07 окт. 2010 г., 19:01
Спасибо @Bozho за ответ на дополнительный вопрос. Хотел бы я дать тебе еще +1. Я разработал это в ответ, чтобы попытаться собрать несколько потенциальных причин.
 Mark Peters07 окт. 2010 г., 17:48
@ Божо: не может быть такой вещи, чтобы вернуть действительный, многоразовый итерируемый, это правда. Но я просто хочу засунуть свой итератор в цикл for, действительный Iterable или нет.
 ColinD07 окт. 2010 г., 18:15
@Mark Peters: Я действительно обратился к этому в своем ответе ... это должно быть очень легко! Тем не менее, Project Lambda официально опустился до 8 JDK в 2012 году.
 Mark Peters07 окт. 2010 г., 18:13
@ Bozho или кто-нибудь еще: я не следил за всей проблемой закрытия. Это можно было бы сделать довольно легко с помощью указателя на любую функцию без аргументов, которая возвращает итератор. Кто-нибудь знает, будет ли Project Lambda, если он превратится в Java 7, поддерживать подобные вещи?

descendingIteratorбыло бы лучше написатьdescendingIterable() метод для возврата нисходящего итерируемого на основе deque-, который в основном занимает место вашего анонимного класса. Это кажется довольно разумным для меня. Согласно предложению Колина, итеративная реализация, возвращаемая этим методом, вызоветdescendingIterator на оригинальном deque каждый раз свойiterator() метод был вызван.

Если вытолько получил итератор и хотел бы сохранить его таким, вам нужно написать реализациюIterable<T> который обернул итератор и вернул егоровно один раз, бросив исключение, еслиiterator() называется не раз. Это сработало бы, но было бы довольно уродливо.

 Mark Peters07 окт. 2010 г., 18:01
@ColinD: я думаю, что ваш первый комментарий - лучший ответ (он отличается от предложения Джона). Попробуйте добавить его в качестве ответа.
 Mark Peters07 окт. 2010 г., 17:21
@JonSkeet: Это хорошая точка зрения оdescendingIterable()и вот как вы обходитесь с Картами: вы можете просто сделатьfor ( Entry e : myMap.entrySet() ), к несчастьюDeque это стандартный библиотечный класс, а не мой класс.
 Mark Peters07 окт. 2010 г., 17:24
Можете ли вы объяснить, зачем вам это условие, еслиiterator() вызывается не раз он должен сгенерировать ошибку? Я предполагаю, что вы говорите так, чтобы, если вы пропускаете ссылки на Iterable, вы не предоставляете устаревшие итераторы?
 Mark Peters07 окт. 2010 г., 17:45
@ColinD: ты прав, это другой важный момент. В любом случае речь идет о предотвращении утечки ссылки на итерируемое. Чувак, становится все более очевидным (для меня в любом случае), что они должны были просто спроектировать проклятый цикл, чтобы принять итератор.
 ColinD07 окт. 2010 г., 18:17
@Mark Peters: я добавил его в качестве ответа, хотя он очень похож на ответ Божо ... просто более приятный синтаксис и не раскрывает класс реализации.
 ColinD07 окт. 2010 г., 17:41
@Mark: AnIterable должен вернуть действительный, свежийIterator каждый раз ... если он не может этого сделать, егоiterator() нельзя допускать, чтобы метод вызывался более одного раза, так как в противном случае он мог бы выдавать несколько ссылок на один и тот жеIterator, Это может привести к чему-то ожидающему, что оно будет перебирать все элементыIterable представляет в то время как на самом деле единыйIterator полностью потрачено и нечего читать, или того хуже. В общем, я думаю, что лучше не пытаться представлятьIterator какIterable.
 Jon Skeet07 окт. 2010 г., 18:17
@Mark: предложение Колина - это то, к чему я стремился; Я явно не достаточно ясно выразился :)
 ColinD07 окт. 2010 г., 17:28
Согласитесь ... вы можете создать свой собственный служебный класс, скажем,Deques и есть метод, как<T> Iterable<T> asDescendingIterable(Deque<? extends T> deque), Это кажется наиболее разумным, поскольку вы получаете правильную (многоразовую) реализациюIterable, Я чувствую, что Guava может иметь что-то подобное, если бы он мог поддерживать Java 1.6.
 Mark Peters07 окт. 2010 г., 17:59
+1 за то, что касается того, почему обертка не так хороша, как кажется.
Почему расширенный цикл for не принимает итератор?

ом, почему цикл for-each не просто принимает итератор.

удобство: Цикл for-each был создан частично для удобства обычной операции по выполнению действия с каждым элементом коллекции. У него нет обязательств или намерений заменить явное использование итераторов (очевидно, если вы хотите удалить элементы, вам нужна явная ссылка на итератор).читабельность: Цикл для каждогоfor ( Row r : table ) предполагается, что он чрезвычайно удобочитаем как "для каждой строки" r "в таблице ...". Проводыfor ( Row r : table.backwardsIterator() ) нарушает эту читаемость.прозрачность: Если объект является одновременноIterable а также Iteratorкакое будет поведение? Хотя легко создать согласованное правило (например, итерируемое до итератора), поведение будет менее прозрачным для разработчиков. Кроме того, это должно быть проверено во время компиляции.Инкапсуляция / ScopeЭто (на мой взгляд) самая важная причина. Цикл for-each предназначен для инкапсуляцииIterator и ограничивает его область до цикла. Это делает цикл «только для чтения» двумя способами: он не отображает итератор, что означает, что нет ничего (легко) осязаемого, если его состояние измененоот цикл, и вы не можете изменить состояние операндав цикл (как вы можете, напрямую взаимодействуя с итератором черезremove()). Передача Итератора самостоятельно обязательно означает, что Итератор выставлен, и вы потеряете оба этих атрибута цикла «только для чтения».
 whiskeysierra07 окт. 2010 г., 22:38
@ Mark Вы правы, давайте надеяться, что Google скоро перейдет на 1.6.
 Anony-Mousse27 июн. 2012 г., 07:52
Я тоже обдумывал это. Но даже тогда это может раздражать, для Generics, когда тип даже несколько динамичен во время компиляции.
 Kevin Bourrillion19 окт. 2010 г., 09:50
Это вряд ли произойдет в ближайшее время.
 Anony-Mousse27 июн. 2012 г., 02:12
На самом деле часть 3 ИМХО не так просто. Потому что это время компиляции против времени выполнения. Объект, который выглядит как Iterator во время компиляции, может оказаться Iterable во время выполнения, и внезапно он ведет себя неожиданно.
 Mark Peters27 июн. 2012 г., 02:36
@Anony: это отличный момент. Я думаю, что единственным выбором было бы основывать его на статическом типе выражения / переменной, так как решение должно быть принято до компиляции (после компиляции, на самом деле нет ничего, что отличает цикл for-each от цикла for, который явно использует итератор; это языковая функция, а не функция байт-кода).
 Mark Peters07 окт. 2010 г., 22:31
@Willi: Если ColinD прав, Guava еще не пытался охватить вещи, добавленные в 1.6. Я ожидаю, что можно добавить несколько вещей. Смотрите также ответ ColinD.
 Kevin Bourrillion07 окт. 2010 г., 22:12
Было бы непонятно, что оператор for будет выглядеть как операция чтения, но будет изменять состояние своего операнда. Сегодня вы можете один раз «просмотреть» список строк, чтобы найти самую длинную длину, а затем повторить ее, чтобы найти все строки этой длины. Но если бы вы изменили этот код, чтобы он вместо этого работал на итераторе, и foreach все еще работал, вы бы в значительной степени скрыли тот факт, что код теперь ужасно нарушен.
 Mark Peters07 окт. 2010 г., 22:20
@ Кевин: Я думаю, что это сочетается с инкапсуляцией и объемом. На самом деле это не операция чтения, поскольку вы все равно можете получить используемый итератор и связываться с ним столько раз, сколько захотите. Текущий цикл foreach только делает это более трудным.
 whiskeysierra07 окт. 2010 г., 22:26
@Kevin Как насчет перегруженной версииIterables.reverse() который занимаетDeque?

идиоматический путь в Java 8 (будучи многословным языком) заключается в следующем:

T>) () -> myDeque.descendingIterator()) {
  // use item
}

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

Конечно, вы всегда можете просто прибегнуть кIterator.forEachRemaining():

myDeque.descendingIterator().forEachRemaining(t -> {
  // use item
});

которые вы можете использовать следующим образом:

import static Iter.*;

for( Element i : iter(elements) ) {
}

for( Element i : iter(o, Element.class) ) {
}

В качестве следующего шага возвращаемый типiter() может быть свободный интерфейс, так что вы можете сделать:

for( Element i : iter(elements).reverse() ) {
}

или, может быть

for( Element i : reverse(elements) ) {
}

Вы также должны взглянуть наop4j который решает многие из этих проблем с очень хорошим API.

 ColinD08 окт. 2010 г., 17:02
Как бы вы изменили произвольноеIterator или жеIterable? У некоторых даже нет конца, с которого вы могли бы начать.
 Aaron Digulla11 окт. 2010 г., 10:12
@ColinD: Java API по умолчанию просто не предлагает достаточно информации для создания обратного итератора, которыйвсегда работает. Итак, то, что я делаю, является следующей лучшей вещью. Если вам не нравится, используйтеListIterator вместо.
 Aaron Digulla08 окт. 2010 г., 18:03
@ColinD: Почему я хотел бы отменить произвольный итератор? 100% всех итераторов в моем коде в конечном итоге завершаются, а 95% - даже быстро. Это оставляет 5%, для которых мне нужен особый случай, но это не делает код бесполезным для остальных 95% (например, когда какой-то старый класс возвращаетEnumeration илиList без универсального типа).
 ColinD08 окт. 2010 г., 18:39
Ваши методы предполагают возможность обращения к любому итератору, хотя, конечно, я предполагаю, что вы могли бы документально подтвердить, что он должен использоваться только с ограниченными итераторами. Но затем, чтобы действительно сделать это, вы должны сначала скопировать содержимое итератора, а затем выполнить итерацию по этой копии в обратном порядке, что кажется расточительным.

конечно, есть решение для обратного повторяемого сценария, но, к сожалению, вам нужны два шага.Iterables.reverse () занимаетList в качестве параметра, а неIterable.

final Iterable<String> it = Arrays.asList("a", "b", "c");
for(final String item : Iterables.reverse(Lists.newArrayList(it))){
    System.out.println(item);
}

Выход:

c
b
a

 Sean Patrick Floyd08 окт. 2010 г., 00:22
Guava поддерживает только Java 1.5, но Deque - дополнение 1.6
 whiskeysierra07 окт. 2010 г., 22:25
Там может быть перегруженная версияIterables.reverse() который занимаетDeque вместоList, Было бы хорошим дополнением.
 Mark Peters07 окт. 2010 г., 17:40
descendingIterator просто используется в качестве примера. Может быть, у вас есть коллекция дерева, гдеiterator() выполняет поиск в глубину иbreadthFirstIterator() выполняет BFS.

ImmutableList.copyOf(Iterator) безопасно преобразовать итератор в итерируемое. Несмотря на кажущуюся простоту зацикливания на итераторе, существуют проблемы, которые скрываются за каждым, и самый безопасный вариант - создать стабильную структуру данных, такую ​​как список.

Это также обсуждается вИдея Кладбище:

Наибольшее беспокойство вызывает то, чтоIterable Обычно предполагается, что он может создавать несколько независимых итераторов. Док не говорит этого, ноCollection Док тоже не говорит об этом, и все же мы предполагаем, что это итераторы. У нас были поломки в Google, когда это предположение было нарушено.

Самый простой обходной путьImmutableList.copyOf(Iterator), который является довольно быстрым, безопасным и обеспечивает много других преимуществ.

Решение Вопроса

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

public class Deques {
  private Deques() {}

  public static <T> Iterable<T> asDescendingIterable(final Deque<T> deque) {
    return new Iterable<T>() {
      public Iterator<T> iterator() {
        return deque.descendingIterator();
      }
    }
  }
}

Это еще один случай, когда действительно плохо, что у нас еще нет лямбд и ссылок на методы. В Java 8 вы сможете написать что-то вроде этого, учитывая, что ссылка на методdescendingIterator() соответствует подписиIterable:

Deque<String> deque = ...
for (String s : deque::descendingIterator) { ... }
 Stuart Marks03 мар. 2016 г., 08:52
Подход Java 8 работает с печальным требованием, что приведение(Iterable<String>) необходимо предоставить целевой тип для ссылки на метод.

Ваш ответ на вопрос