代码之家  ›  专栏  ›  技术社区  ›  Mark Peters

对于给定迭代器的每个循环使用惯用方法?

  •  29
  • Mark Peters  · 技术社区  · 14 年前

    当增强的for循环(Frach循环)被添加到Java时,它被用来与数组或数组的目标一起工作。 Iterable .

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

    对于只实现一种迭代类型的集合类来说,它非常有用,因此 iterator() 方法。

    但是,我发现自己非常沮丧,有时我想使用集合类中的非标准迭代器。例如,我最近试图帮助某人使用 Deque 作为后进先出/堆栈,然后按FIFO顺序打印元素。我被迫这样做:

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

    我失去了for each循环的优势。不仅仅是按键。如果不需要的话,我不喜欢公开迭代器,因为调用迭代器很容易出错 it.next() 两次等。

    现在理想情况下,我认为for each循环应该接受 Iterator 也。但事实并非如此。在这种情况下,是否有一种惯用的方法来使用for each循环?我也很乐意听到一些建议,比如使用像番石榴这样的公共图书馆。

    在没有助手方法/类的情况下,我能想到的最好方法是:

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

    这不值得使用。

    我想看看番石榴有什么 Iterables.wrap 使这个成语,但没有找到类似的东西。显然,我可以通过类或帮助器方法来滚动我自己的迭代器包装器。还有其他想法吗?

    编辑: 作为旁注,是否有人能给出一个合理的理由来解释为什么增强的for循环不应该只接受 迭代器 ?让我接受当前的设计可能要花很长的时间。

    9 回复  |  直到 5 年前
        1
  •  15
  •   ColinD    11 年前

    我可能会做一个叫做 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();
          }
        }
      }
    }
    

    这是另一个非常糟糕的情况,我们还没有lambda和方法引用。在Java 8中,给定方法引用,您将能够编写类似的内容。 descendingIterator() 匹配的签名 Iterable :

    Deque<String> deque = ...
    for (String s : deque::descendingIterator) { ... }
    
        2
  •  26
  •   Mark Peters    12 年前

    为什么增强的for循环不接受迭代器?

    我想从各种答案中收集一些潜在的原因,来解释为什么for each循环不简单地接受迭代器。

    1. 便利性 :for each循环的部分创建是为了方便执行给定集合中每个元素的操作。它没有替换迭代器显式使用的义务或意图(显然,如果要删除元素,则需要显式引用迭代器)。
    2. 可读性 :for each循环 for ( Row r : table ) 是指“对于表中的每一行”r“,可读性极高。看到 for ( Row r : table.backwardsIterator() ) 打破了可读性。
    3. 透明性 :如果一个对象同时是 Iterable Iterator ,行为如何?虽然很容易制定一致的规则(例如,在迭代器之前是ITerable),但是对于开发人员来说,行为将变得不那么透明。此外,还必须在编译时检查这一点。
    4. 封装/范围 :这是(在我看来)最重要的原因。for each循环旨在封装 迭代器 并将其范围限制为循环。这使得循环以两种方式“只读”:它不公开迭代器,这意味着没有任何(容易)有形的东西改变了它的状态。 通过 循环,也不能更改操作数的状态 在里面 循环(可以通过直接与迭代器接口 remove() )亲自传递迭代器必然意味着迭代器是公开的,这会使您丢失循环的两个“只读”属性。
        3
  •  9
  •   Jon Skeet    14 年前

    而不是创建一个 descendingIterator ,最好写一个 descendingIterable() 方法返回一个基于deque的降序iterable,它基本上取代了匿名类。我觉得这很合理。根据Colin的建议,此方法返回的iterable实现将调用 下降振荡器 在原来的德克上每次都有自己的 iterator() 方法被调用。

    如果你已经 只有 得到一个迭代器并希望保持这种状态,您必须编写一个 Iterable<T> 它包装了迭代器并返回它 恰好一次 ,如果 迭代器() 多次调用。这是可行的,但显然是相当难看的。

        4
  •  4
  •   dimo414    8 年前

    番石榴用户可以做到 ImmutableList.copyOf(Iterator) 将迭代器安全地转换为ITerable。尽管在迭代器上循环看起来很简单,但有人担心foreach会隐藏起来,最安全的选择是创建一个稳定的数据结构,如列表。

    这也在 Idea Graveyard :

    最大的问题是 Iterable 通常假定能够生成多个独立的迭代器。医生没有这么说,但是 Collection Doc也没有这么说,但是我们假定它是迭代器。当这个假设被违反时,我们在谷歌已经崩溃了。

    最简单的解决方法是 不可变列表.copyof(迭代器) 这是相当快,安全,并提供了许多其他的优势。

        5
  •  3
  •   Bozho    14 年前
    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 ,但是如果传递的对象同时实现了这两个呢?(很奇怪,但还是有可能的)。

        6
  •  3
  •   Lukas Eder    8 年前

    这个 惯用的 Java 8的方式(作为一种冗长的语言)是这样的:

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

    即包装 Iterator 在一个 Iterable 拉姆达这基本上就是你自己使用匿名类所做的,但是lambda有点好。

    当然,你可以一直使用 Iterator.forEachRemaining() :

    myDeque.descendingIterator().forEachRemaining(t -> {
      // use item
    });
    
        7
  •  1
  •   Sean Patrick Floyd    14 年前

    当然,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

        8
  •  0
  •   Aaron Digulla    14 年前

    我建议使用工厂方法创建一个助手类,您可以这样使用:

    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
  •  0
  •   Haldean Brown    5 年前

    Apache Commons Collections API具有一个名为 IteratorIterable 要做到这一点:

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