在给定迭代器的情况下使用for-each循环的惯用方法?

时间:2021-07-13 20:47:17

When the enhanced for loop (foreach loop) was added to Java, it was made to work with a target of either an array or Iterable.

当增强的for循环(foreach循环)被添加到Java时,它可以使用数组或Iterable的目标。

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

That works great for Collection classes that only implement one type of iteration, and thus have a single iterator() method.

这对于仅实现一种迭代类型的Collection类非常有用,因此只有一个iterator()方法。

But I find myself incredibly frustrated the odd time I want to use a non-standard iterator from a Collection class. For example, I was recently trying to help somebody use a Deque as a LIFO/stack but then print the elements in FIFO order. I was forced to do this:

但是我发现自己非常沮丧地想要使用Collection类中的非标准迭代器。例如,我最近试图帮助某人使用Deque作为LIFO /堆栈,然后按FIFO顺序打印元素。我*这样做:

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

I lose the advantages of the for-each loop. It's not just about keystrokes. I don't like exposing the iterator if I don't have to, since it's easy to make the mistake of calling it.next() twice, etc.

我失去了for-each循环的优点。这不仅仅是击键。如果我不需要,我不喜欢暴露迭代器,因为很容易犯两次调用it.next()的错误,等等。

Now ideally I think the for-each loop should have accepted an Iterator as well. But it doesn't. So is there an idiomatic way of using the for-each loop in these circumstances? I'd also love to hear suggestions that use common collections libraries like Guava.

理想情况下,我认为for-each循环也应该接受迭代器。但事实并非如此。那么在这些情况下是否存在使用for-each循环的惯用方法?我也很想听到使用像Guava这样的常见集合库的建议。

The best I can come up with in absense of a helper method/class is:

我能想出的最好的帮助方法/类是:

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

Which isn't worth using.

哪个不值得使用。

I'd love to see Guava have something like Iterables.wrap to make this idiomatic, but didn't find anything like that. Obviously I could roll my own Iterator wrapper via a class or helper method. Any other ideas?

我很想看到Guava有像Iterables.wrap这样的东西来使这个惯用,但没有找到类似的东西。显然,我可以通过类或辅助方法滚动自己的Iterator包装器。还有其他想法吗?

Edit: As a side-note, can anybody give a valid reason for why the enhanced for-loop shouldn't have been able to just accept an Iterator? It would probably go a long way to making me live with the current design.

编辑:作为旁注,任何人都可以给出一个有效的理由,说明为什么增强的for循环不应该只接受迭代器?让我与当前设计一起生活可能还有很长的路要走。

8 个解决方案

#1


15  

What I'd probably do is just make a utility class called Deques which could support this, along with other utilities if desired.

我可能会做的只是创建一个名为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();
      }
    }
  }
}

This is another case where it's really too bad we don't have lambdas and method references yet. In Java 8, you'll be able to write something like this given that the method reference descendingIterator() matches the signature of Iterable:

这是另一种情况,我们还没有lambdas和方法引用真的太糟糕了。在Java 8中,您将能够编写类似这样的内容,因为方法引用descendingIterator()与Iterable的签名匹配:

Deque<String> deque = ...
for (String s : deque::descendingIterator) { ... }

#2


25  

Why doesn't the enhanced for loop just accept an iterator?

I want to gather a few of the potential reasons from the various answers as to why the for-each loop doesn't simply accept an iterator.

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

  1. Convenience: The for-each loop was created partly for convenience for the common operation of performing an action given each element of a collection. It has no obligation or intention of replacing the explicit use of iterators (obviously if you want to remove elements, you need an explicit reference to the iterator).
  2. 便利性:for-each循环的创建部分是为了方便执行给定集合的每个元素的操作的常见操作。它没有义务或意图替换显式使用迭代器(显然,如果要删除元素,则需要显式引用迭代器)。

  3. Readability: The for-each loop for ( Row r : table ) is meant to be extremely readable as "for each row "r" in table...". Seeing for ( Row r : table.backwardsIterator() ) breaks that readability.
  4. 可读性:(行r:表)的for-each循环意味着极其可读,因为“对于表中的每一行”r“......”。 See for(Row r:table.backwardsIterator())打破了这种可读性。

  5. Transparency: If an object is both an Iterable and an Iterator, what will be the behaviour? Though it's easy to make a consistent rule (e.g. Iterable before Iterator) the behaviour will be less transparent to developers. Furthermore, this will have to be checked at compile time.
  6. 透明度:如果一个对象既是Iterable又是Iterator,那么行为是什么?虽然很容易制定一致的规则(例如Iterator之前的Iterable),但开发人员的行为将变得不那么透明。此外,必须在编译时检查。

  7. Encapsulation/Scope: This is (in my opinion) the most important reason. The for-each loop is designed to encapsulate the Iterator and limits its scope to the loop. This makes the loop "read-only" in two ways: it doesn't expose the iterator, meaning there is nothing (easily) tangible that has its state altered by the loop, nor can you alter the state of the operand in the loop (as you can by interfacing directly with an Iterator via remove()). Passing the Iterator yourself necessarily means the Iterator is exposed, making you lose both of those "read-only" attributes of the loop.
  8. 封装/范围:这是(在我看来)最重要的原因。 for-each循环旨在封装Iterator并将其范围限制为循环。这使得循环以两种方式“只读”:它不暴露迭代器,这意味着没有(容易)有形的东西,它的状态由循环改变,也不能改变循环中操作数的状态(正如你可以通过remove()直接与Iterator接口)。自己传递迭代器必然意味着迭代器被暴露,使你失去了循环的那些“只读”属性。

#3


9  

Rather than create a descendingIterator, it would be better to write a descendingIterable() method to return a descending iterable based on a deque- which basically takes the place of your anonymous class. That seems pretty reasonable to me. As per Colin's suggestion, the iterable implementation returned by this method would call descendingIterator on the original deque each time its own iterator() method was called.

而不是创建一个descendingIterator,最好编写一个descendingIterable()方法来返回一个基于deque的降序迭代 - 它基本上取代了你的匿名类。这对我来说似乎很合理。根据Colin的建议,每次调用自己的iterator()方法时,此方法返回的可迭代实现将在原始deque上调用descendingIterator。

If you've only got an iterator and want to keep it that way, you'd have to write an implementation of Iterable<T> which wrapped the iterator and returned it exactly once, throwing an exception if iterator() is called more than once. That would work, but it would clearly be pretty ugly.

如果你只有一个迭代器并希望保持这种方式,你必须编写一个Iterable 的实现,它包装迭代器并返回一次,如果调用iterator()则抛出异常一旦。那会有用,但显然很难看。

#4


4  

Guava users can do ImmutableList.copyOf(Iterator) to safely convert an Iterator into an Iterable. Despite the seeming simplicity of looping over an Iterator, there are concerns that foreach hides, and the safest option is to create a stable data structure like a list.

Guava用户可以使用ImmutableList.copyOf(Iterator)将Iterator安全地转换为Iterable。尽管在Iterator上循环似乎很简单,但人们担心foreach会隐藏,而最安全的选择是创建一个像列表一样的稳定数据结构。

This is also discussed in the Idea Graveyard:

这也在Idea Graveyard中讨论过:

The biggest concern is that Iterable is generally assumed to be able to produce multiple independent iterators. The doc doesn't say this, but the Collection doc doesn't say this, either, and yet we assume it of its iterators. We have had breakages in Google when this assumption was violated.

最大的担忧是Iterable通常被认为能够生成多个独立的迭代器。文档没有说明这一点,但是收集文档也没有说明这一点,但我们假设它的迭代器。当违反此假设时,我们在谷歌遭遇破产。

The simplest workaround is ImmutableList.copyOf(Iterator), which is pretty fast, safe, and provides many other advantages besides.

最简单的解决方法是ImmutableList.copyOf(Iterator),它非常快速,安全,并提供许多其他优点。

#5


3  

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();
    }
}

And then

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

}

So, for each such case, you'd need a special adapter. I don't think it is theoretically possible to do what you want, because the facility has to know what iterator-returning methods exist, so that it can call them.

因此,对于每种情况,您都需要一个特殊的适配器。我认为理论上不可能做你想做的事情,因为设施必须知道迭代器返回方法的存在,以便它可以调用它们。

As for your additional question - I believe because the for-each loop was actually meant to make things shorter for general-purpose scenarios. And calling an additional method makes the syntax more verbose. It could've supported both Iterable and Iterator, but what if the object passed implemented both? (would be odd, but still possible).

至于你的另外一个问题 - 我相信因为for-each循环实际上是为了使通用场景更短。并且调用另一个方法会使语法更加冗长。它可以同时支持Iterable和Iterator,但是如果传递的对象都实现了呢? (会很奇怪,但仍有可能)。

#6


1  

Guava does of course have a solution for the reverse iterable scenario, but unfortunately you need two Steps. Iterables.reverse() takes a List as parameter, not an Iterable.

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);
}

Output:

c
b
a

c b a

#7


1  

The idiomatic way in Java 8 (being a verbose language) is this:

Java 8中的惯用方法(是一种冗长的语言)是这样的:

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

I.e. wrap the Iterator in an Iterable lambda. This is pretty much what you did yourself using an anonymous class, but it's a bit nicer with the lambda.

即将Iterator包装在Iterable lambda中。这几乎就是你使用匿名类所做的事情,但它对lambda来说更好一些。

Of course, you could always just resort to using Iterator.forEachRemaining():

当然,你总是可以使用Iterator.forEachRemaining():

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

#8


0  

I suggest to create a helper class with factory methods that you can use like this:

我建议用工厂方法创建一个helper类,你可以像这样使用:

import static Iter.*;

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

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

As a next step, the return type of iter() could be a fluent interface so you can do:

下一步,iter()的返回类型可以是一个流畅的界面,所以你可以这样做:

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

or maybe

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

You should also have a look at op4j which solves many of these problems with a very nice API.

您还应该看看op4j,它通过一个非常好的API解决了许多这些问题。

#1


15  

What I'd probably do is just make a utility class called Deques which could support this, along with other utilities if desired.

我可能会做的只是创建一个名为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();
      }
    }
  }
}

This is another case where it's really too bad we don't have lambdas and method references yet. In Java 8, you'll be able to write something like this given that the method reference descendingIterator() matches the signature of Iterable:

这是另一种情况,我们还没有lambdas和方法引用真的太糟糕了。在Java 8中,您将能够编写类似这样的内容,因为方法引用descendingIterator()与Iterable的签名匹配:

Deque<String> deque = ...
for (String s : deque::descendingIterator) { ... }

#2


25  

Why doesn't the enhanced for loop just accept an iterator?

I want to gather a few of the potential reasons from the various answers as to why the for-each loop doesn't simply accept an iterator.

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

  1. Convenience: The for-each loop was created partly for convenience for the common operation of performing an action given each element of a collection. It has no obligation or intention of replacing the explicit use of iterators (obviously if you want to remove elements, you need an explicit reference to the iterator).
  2. 便利性:for-each循环的创建部分是为了方便执行给定集合的每个元素的操作的常见操作。它没有义务或意图替换显式使用迭代器(显然,如果要删除元素,则需要显式引用迭代器)。

  3. Readability: The for-each loop for ( Row r : table ) is meant to be extremely readable as "for each row "r" in table...". Seeing for ( Row r : table.backwardsIterator() ) breaks that readability.
  4. 可读性:(行r:表)的for-each循环意味着极其可读,因为“对于表中的每一行”r“......”。 See for(Row r:table.backwardsIterator())打破了这种可读性。

  5. Transparency: If an object is both an Iterable and an Iterator, what will be the behaviour? Though it's easy to make a consistent rule (e.g. Iterable before Iterator) the behaviour will be less transparent to developers. Furthermore, this will have to be checked at compile time.
  6. 透明度:如果一个对象既是Iterable又是Iterator,那么行为是什么?虽然很容易制定一致的规则(例如Iterator之前的Iterable),但开发人员的行为将变得不那么透明。此外,必须在编译时检查。

  7. Encapsulation/Scope: This is (in my opinion) the most important reason. The for-each loop is designed to encapsulate the Iterator and limits its scope to the loop. This makes the loop "read-only" in two ways: it doesn't expose the iterator, meaning there is nothing (easily) tangible that has its state altered by the loop, nor can you alter the state of the operand in the loop (as you can by interfacing directly with an Iterator via remove()). Passing the Iterator yourself necessarily means the Iterator is exposed, making you lose both of those "read-only" attributes of the loop.
  8. 封装/范围:这是(在我看来)最重要的原因。 for-each循环旨在封装Iterator并将其范围限制为循环。这使得循环以两种方式“只读”:它不暴露迭代器,这意味着没有(容易)有形的东西,它的状态由循环改变,也不能改变循环中操作数的状态(正如你可以通过remove()直接与Iterator接口)。自己传递迭代器必然意味着迭代器被暴露,使你失去了循环的那些“只读”属性。

#3


9  

Rather than create a descendingIterator, it would be better to write a descendingIterable() method to return a descending iterable based on a deque- which basically takes the place of your anonymous class. That seems pretty reasonable to me. As per Colin's suggestion, the iterable implementation returned by this method would call descendingIterator on the original deque each time its own iterator() method was called.

而不是创建一个descendingIterator,最好编写一个descendingIterable()方法来返回一个基于deque的降序迭代 - 它基本上取代了你的匿名类。这对我来说似乎很合理。根据Colin的建议,每次调用自己的iterator()方法时,此方法返回的可迭代实现将在原始deque上调用descendingIterator。

If you've only got an iterator and want to keep it that way, you'd have to write an implementation of Iterable<T> which wrapped the iterator and returned it exactly once, throwing an exception if iterator() is called more than once. That would work, but it would clearly be pretty ugly.

如果你只有一个迭代器并希望保持这种方式,你必须编写一个Iterable 的实现,它包装迭代器并返回一次,如果调用iterator()则抛出异常一旦。那会有用,但显然很难看。

#4


4  

Guava users can do ImmutableList.copyOf(Iterator) to safely convert an Iterator into an Iterable. Despite the seeming simplicity of looping over an Iterator, there are concerns that foreach hides, and the safest option is to create a stable data structure like a list.

Guava用户可以使用ImmutableList.copyOf(Iterator)将Iterator安全地转换为Iterable。尽管在Iterator上循环似乎很简单,但人们担心foreach会隐藏,而最安全的选择是创建一个像列表一样的稳定数据结构。

This is also discussed in the Idea Graveyard:

这也在Idea Graveyard中讨论过:

The biggest concern is that Iterable is generally assumed to be able to produce multiple independent iterators. The doc doesn't say this, but the Collection doc doesn't say this, either, and yet we assume it of its iterators. We have had breakages in Google when this assumption was violated.

最大的担忧是Iterable通常被认为能够生成多个独立的迭代器。文档没有说明这一点,但是收集文档也没有说明这一点,但我们假设它的迭代器。当违反此假设时,我们在谷歌遭遇破产。

The simplest workaround is ImmutableList.copyOf(Iterator), which is pretty fast, safe, and provides many other advantages besides.

最简单的解决方法是ImmutableList.copyOf(Iterator),它非常快速,安全,并提供许多其他优点。

#5


3  

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();
    }
}

And then

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

}

So, for each such case, you'd need a special adapter. I don't think it is theoretically possible to do what you want, because the facility has to know what iterator-returning methods exist, so that it can call them.

因此,对于每种情况,您都需要一个特殊的适配器。我认为理论上不可能做你想做的事情,因为设施必须知道迭代器返回方法的存在,以便它可以调用它们。

As for your additional question - I believe because the for-each loop was actually meant to make things shorter for general-purpose scenarios. And calling an additional method makes the syntax more verbose. It could've supported both Iterable and Iterator, but what if the object passed implemented both? (would be odd, but still possible).

至于你的另外一个问题 - 我相信因为for-each循环实际上是为了使通用场景更短。并且调用另一个方法会使语法更加冗长。它可以同时支持Iterable和Iterator,但是如果传递的对象都实现了呢? (会很奇怪,但仍有可能)。

#6


1  

Guava does of course have a solution for the reverse iterable scenario, but unfortunately you need two Steps. Iterables.reverse() takes a List as parameter, not an Iterable.

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);
}

Output:

c
b
a

c b a

#7


1  

The idiomatic way in Java 8 (being a verbose language) is this:

Java 8中的惯用方法(是一种冗长的语言)是这样的:

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

I.e. wrap the Iterator in an Iterable lambda. This is pretty much what you did yourself using an anonymous class, but it's a bit nicer with the lambda.

即将Iterator包装在Iterable lambda中。这几乎就是你使用匿名类所做的事情,但它对lambda来说更好一些。

Of course, you could always just resort to using Iterator.forEachRemaining():

当然,你总是可以使用Iterator.forEachRemaining():

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

#8


0  

I suggest to create a helper class with factory methods that you can use like this:

我建议用工厂方法创建一个helper类,你可以像这样使用:

import static Iter.*;

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

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

As a next step, the return type of iter() could be a fluent interface so you can do:

下一步,iter()的返回类型可以是一个流畅的界面,所以你可以这样做:

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

or maybe

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

You should also have a look at op4j which solves many of these problems with a very nice API.

您还应该看看op4j,它通过一个非常好的API解决了许多这些问题。