Подтвердить что ты не робот

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

Когда расширенный цикл цикла (цикл 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() дважды и т.д.

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

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

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

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

  • Удобство. Для каждого цикла был создан частично для удобства для общей операции выполнения действия, данного каждому элементу коллекции. Он не обязан или намерен заменить явное использование итераторов (очевидно, если вы хотите удалить элементы, вам нужна явная ссылка на итератор).
  • Чтение. Цикл for-each for ( Row r : table ) должен быть чрезвычайно читаемым как "для каждой строки" r "в таблице...". Наблюдение for ( Row r : table.backwardsIterator() ) нарушает эту удобочитаемость.
  • Прозрачность. Если объект является как Iterable и a Iterator, каково будет поведение? Хотя легко сделать последовательное правило (например, Iterable before Iterator), поведение будет менее прозрачным для разработчиков. Кроме того, это нужно будет проверить во время компиляции.
  • Инкапсуляция/область действия. Это (на мой взгляд) самая важная причина. Цикл for-each предназначен для инкапсуляции Iterator и ограничения его объема на цикл. Это делает цикл "только для чтения" двумя способами: он не выставляет итератор, что означает, что нет ничего (легко) материального, которое имеет свое состояние, измененное циклом, и вы не можете изменить состояние операнда в цикле (как вы можете, взаимодействуя непосредственно с Iterator через remove()). Прохождение Итератора обязательно означает, что Итератор разоблачен, заставляя вас потерять оба этих атрибута "только для чтения" цикла.

Ответ 3

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

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

Ответ 4

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

Это также обсуждается в Idea Graveyard:

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

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

Ответ 5

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

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

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

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

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

Ответ 6

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, но что, если переданный объект реализовал оба? (было бы странно, но все же возможно).

Ответ 7

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

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

Вывод:

с
б
а

Ответ 8

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

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.

Ответ 9

В API коллекций Apache Commons есть класс под названием IteratorIterable, который делает именно это:

Iterator<X> iter;
for (X item : new IteratorIterable(iter)) {
    ...
}