有没有办法在Java的for-each循环中访问迭代计数器?
有没有在Java的每个循环的方式
for(String s : stringArray) { doSomethingWith(s); }
找出循环已经被处理了多久?
除了使用旧的和已知for(int i=0; i < boundary; i++)
– 循环,是构造
int i = 0; for(String s : stringArray) { doSomethingWith(s); i++; }
唯一的方法是在每个循环中都有这样一个计数器?
不,但你可以提供你自己的计数器。
原因是for-each循环内部没有计数器; 它基于Iterable接口,即它使用Iterator
来循环“集合” – 可能根本不是集合,实际上可能根本不是基于索引(如链接列表)的东西。
还有另一种方法。
鉴于您编写自己的Index
类和一个静态方法,可以通过这个类的实例返回一个Iterable
for (Index<String> each: With.index(stringArray)) { each.value; each.index; ... }
With.index
的实现类似于
class With { public static <T> Iterable<Index<T>> index(final T[] array) { return new Iterable<Index<T>>() { public Iterator<Index<T>> iterator() { return new Iterator<Index<T>>() { index = 0; public boolean hasNext() { return index < array.size } public Index<T> next() { return new Index(array[index], index++); } ... } } } } }
最简单的解决scheme就是运行自己的计数器:
int i = 0; for (String s : stringArray) { doSomethingWith(s, i); i++; }
原因是因为没有实际的保证集合中的项目(即迭代的变体)甚至有索引,甚至没有定义的顺序 (当添加或删除元素时,某些集合可能会改变顺序)。
例如,看下面的代码:
import java.util.*; public class TestApp { public static void AddAndDump(AbstractSet<String> set, String str) { System.out.println("Adding [" + str + "]"); set.add(str); int i = 0; for(String s : set) { System.out.println(" " + i + ": " + s); i++; } } public static void main(String[] args) { AbstractSet<String> coll = new HashSet<String>(); AddAndDump(coll, "Hello"); AddAndDump(coll, "My"); AddAndDump(coll, "Name"); AddAndDump(coll, "Is"); AddAndDump(coll, "Pax"); } }
当你运行的时候,你可以看到像这样的东西:
Adding [Hello] 0: Hello Adding [My] 0: Hello 1: My Adding [Name] 0: Hello 1: My 2: Name Adding [Is] 0: Hello 1: Is 2: My 3: Name Adding [Pax] 0: Hello 1: Pax 2: Is 3: My 4: Name
这表明,正确的说,顺序不被视为一个集合的显着特征。
还有其他方法可以在没有手动计数器的情况下进行,但对于可疑的益处来说,这是相当可观的工作。
在Java 8中使用lambdas和函数接口使创build新的循环抽象成为可能。 我可以通过索引和集合大小来循环集合:
List<String> strings = Arrays.asList("one", "two","three","four"); forEach(strings, (x, i, n) -> System.out.println("" + (i+1) + "/"+n+": " + x));
哪些产出:
1/4: one 2/4: two 3/4: three 4/4: four
我实现的是:
@FunctionalInterface public interface LoopWithIndexAndSizeConsumer<T> { void accept(T t, int i, int n); } public static <T> void forEach(Collection<T> collection, LoopWithIndexAndSizeConsumer<T> consumer) { int index = 0; for (T object : collection){ consumer.accept(object, index++, collection.size()); } }
可能性是无止境的。 例如,我创build了一个仅为第一个元素使用特殊函数的抽象:
forEachHeadTail(strings, (head) -> System.out.print(head), (tail) -> System.out.print(","+tail));
其中正确输出逗号分隔列表:
one,two,three,four
我实现的是:
public static <T> void forEachHeadTail(Collection<T> collection, Consumer<T> headFunc, Consumer<T> tailFunc) { int index = 0; for (T object : collection){ if (index++ == 0){ headFunc.accept(object); } else{ tailFunc.accept(object); } } }
图书馆会开始做这些事情,或者你可以推出自己的东西。
恐怕这是不可能的,与foreach
。 但我可以build议你一个简单的旧式for循环 :
List<String> l = new ArrayList<String>(); l.add("a"); l.add("b"); l.add("c"); l.add("d"); // the array String[] array = new String[l.size()]; for(ListIterator<String> it =l.listIterator(); it.hasNext() ;) { array[it.nextIndex()] = it.next(); }
请注意, List接口可以访问it.nextIndex()
。
(编辑)
给你改变的例子:
for(ListIterator<String> it =l.listIterator(); it.hasNext() ;) { int i = it.nextIndex(); doSomethingWith(it.next(), i); }
Sun为Java7考虑的其中一个变化是在foreach循环中提供对内部Iterator
访问。 语法将是这样的(如果这被接受):
for (String str : list : it) { if (str.length() > 100) { it.remove(); } }
这是句法糖,但显然这个function有很多要求。 但是在批准之前,您必须自己计算迭代次数,或者使用Iterator
的常规循环。
Java 8引入了Iterable#forEach()
/ Map#forEach()
方法,与“经典”for-each循环相比,对于许多Collection
/ Map
实现来说效率更高。 但是,在这种情况下也不提供索引。 这里的技巧是在lambdaexpression式之外使用AtomicInteger
。 注意:在lambdaexpression式中使用的variables必须是有效的,这就是为什么我们不能使用普通的int
。
final AtomicInteger indexHolder = new AtomicInteger(); map.forEach((k, v) -> { final int index = indexHolder.getAndIncrement(); // use the index });
如果你在for-each循环中需要一个计数器,你必须自己计数。 据我所知,没有内置的柜台。
有一个“变种”pax的答案… 😉
int i = -1; for(String s : stringArray) { doSomethingWith(s, ++i); }
int i = 0; for(String s : stringArray) { doSomethingWith(s,i); ++i; }
有很多方法,但方法是其中之一
习惯解决scheme:
final Set<Double> doubles; // boilerplate final Iterator<Double> iterator = doubles.iterator(); for (int ordinal = 0; iterator.hasNext(); ordinal++) { System.out.printf("%d:%f",ordinal,iterator.next()); System.out.println(); }
这实际上是Google在Guava讨论中为他们没有提供
CountingIterator
原因所build议的解决scheme。
我发现使用一个简单的循环与增量索引是最有效的,可靠的解决scheme,发布在这里: https : //stackoverflow.com/a/3431543/2430549
对于偶尔需要索引的情况,比如在catch子句中,我有时会使用indexOf。
for(String s : stringArray) { try { doSomethingWith(s); } catch (Exception e) { LOGGER.warn("Had some kind of problem with string " + stringArray.indexOf(s) + ": " + s, e); } }
我有点惊讶没有人build议以下(我承认这是一个懒惰的方法…); 如果stringArray是某种types的List,那么可以使用类似于stringArray.indexOf(S)的方法来为当前计数返回一个值。
注意:这里假定列表的元素是唯一的,或者它们是否是非唯一的并不重要(因为在这种情况下它将返回find的第一个副本的索引)。
有些情况下,这足以…