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

Когда расширенный цикл 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
}

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

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

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

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

Что не стоит использовать.

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

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

Ответы [ 9 ]

26 голосов
/ 07 октября 2010

Почему расширенный цикл for не принимает итератор?

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

  1. Удобство : цикл for-each был создан частично для удобства обычной операции по выполнению действия для каждого элемента коллекции. У него нет обязательств или намерений заменить явное использование итераторов (очевидно, если вы хотите удалить элементы, вам нужна явная ссылка на итератор).
  2. Удобочитаемость : цикл for-each for ( Row r : table ) должен быть чрезвычайно удобочитаемым как "для каждой строки" r "в таблице ...". Видение for ( Row r : table.backwardsIterator() ) нарушает читабельность.
  3. Прозрачность : Если объект одновременно является Iterable и и Iterator, каково будет поведение? Хотя легко создать согласованное правило (например, итерируемое до итератора), поведение будет менее прозрачным для разработчиков. Кроме того, это нужно будет проверить во время компиляции.
  4. Encapsulation / Scope : Это (на мой взгляд) самая важная причина. Цикл for-each предназначен для инкапсуляции Iterator и ограничивает его область действия циклом. Это делает цикл «только для чтения» двумя способами: он не отображает итератор, то есть нет ничего (легко) материального, чье состояние изменено на цикла, и вы не можете изменить состояние операнда в цикла (как вы можете, напрямую взаимодействуя с Итератором через remove()). Передача Итератора сама по себе обязательно означает, что Итератор открыт, и вы потеряете оба этих атрибута цикла «только для чтения».
15 голосов
/ 07 октября 2010

Что я, вероятно, сделал бы, так это просто сделал бы служебный класс с именем 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) { ... }
9 голосов
/ 07 октября 2010

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

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

4 голосов
/ 05 февраля 2013

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

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

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

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

3 голосов
/ 21 декабря 2016

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

for (T t : (Iterable<T>) () -> myDeque.descendingIterator()) {
  // use item
}

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

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

myDeque.descendingIterator().forEachRemaining(t -> {
  // use item
});
3 голосов
/ 07 октября 2010
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, но что, если переданный объект реализовал оба? (было бы странно, но все же возможно).

1 голос
/ 07 октября 2010

У Guava, конечно, есть решение для обратного повторяемого сценария, но, к сожалению, вам нужно два шага. 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

0 голосов
/ 26 марта 2019

API-интерфейс Apache Commons Collections имеет класс IteratorIterable, чтобы сделать это:

Iterator<X> iter;
for (X item : new IteratorIterable(iter)) {
    ...
}
0 голосов
/ 08 октября 2010

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

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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...