3年前,这里也提出了类似的问题: Is there a Java equivalent of Python's 'enumerate' function?
我非常感谢listIterator()
解决方案。不过,我现在对新流和lambdas(在JDK 8中引入)做了很多工作并且想知道:有没有一种优雅的方法来获取当前处理的元素的索引?我的下面列出了,但我觉得它并不特别吸引人。
IntStream.range(0, myList.size())
.mapToObj(i -> doSthWith(myList.get(i), i));
答案 0 :(得分:15)
此问题之前有过几个问题。关键的观察是,除非你有完美的大小和拆分信息(基本上,如果你的源是一个数组),那么这将是一个仅序贯操作。
"没有吸引力"回答你的建议:
IntStream.range(0, myList.size())
.mapToObj(i -> doSthWith(myList.get(i), i));
当myList
是ArrayList
或其他具有快速O(1)索引获取操作的列表并且干净地并行化时,实际上非常有效。所以我认为它没有任何问题。
答案 1 :(得分:6)
可以有一些解决方法。
您可以使用AtomicInteger
每次浏览流的元素时都会增加
final AtomicInteger atom = new AtomicInteger();
list.stream().forEach(i -> System.out.println(i+"-"+atom.getAndIncrement()));
或者使用来自另一个流的迭代器来获取索引(有点像你最初的想法)但更有效率,因为你没有在列表上调用get
。
final Iterator<Integer> a = IntStream.range(0, list.size()).iterator();
list.stream().forEach(i -> System.out.println(i+"-"+a.next()));
嗯,我不确定是否存在其他更好的选择(当然),但这就是我现在的想法。
请注意,假设您没有使用并行流。在后一种情况下,不可能这样做以获得元素与列表中原始索引的映射。
答案 2 :(得分:4)
如果您想要一个适用于非RandomAccess
列表的解决方案,您可以自己编写一个简单的实用方法:
public static <T> void forEach(List<T> list, BiConsumer<Integer,? super T> c) {
Objects.requireNonNull(c);
if(list.isEmpty()) return;
if(list instanceof RandomAccess)
for(int i=0, num=list.size(); i<num; i++)
c.accept(i, list.get(i));
else
for(ListIterator<T> it=list.listIterator(); it.hasNext(); ) {
c.accept(it.nextIndex(), it.next());
}
}
然后您可以像forEach(list, (i,s)->System.out.println(i+"\t"+s));
如果你交换元素和索引的顺序,你可以使用ObjIntConsumer
而不是BiConsumer<Integer,? super T>
来避免潜在的拳击开销:
public static <T> void forEach(List<T> list, ObjIntConsumer<? super T> c) {
Objects.requireNonNull(c);
if(list.isEmpty()) return;
if(list instanceof RandomAccess)
for(int i=0, num=list.size(); i<num; i++)
c.accept(list.get(i), i);
else
for(ListIterator<T> it=list.listIterator(); it.hasNext(); ) {
c.accept(it.next(), it.previousIndex());
}
}
然后,像forEach(list, (s,i) -> System.out.println(i+"\t"+s));
...
答案 3 :(得分:2)
您提供的链接中的python示例是:
>>> numbers = ["zero", "one", "two"]
>>> for i, s in enumerate(numbers):
... print i, s
...
0 zero
1 one
2 two
有几种方法可以使用Java 8实现相同的输出,例如:
String[] numbers = {"zero", "one", "two"};
IntStream.range(0, numbers.length)
.mapToObj(i -> i + " " + numbers[i])
.forEach(System.out::println);
输出:
0 zero
1 one
2 two
答案 4 :(得分:2)
如果你想要更清晰的语法,那么只需结束一些调用:
public class IndexedValue<T> {
public final int index;
public final T value;
public IndexedValue(int index, T value) {
this.index = index;
this.value = value;
}
}
public class Itertools {
public static <T> Stream<IndexedValue<T>> enumerate(ArrayList<T> lst) {
return IntStream.range(0, lst.size())
.mapToObj(i -> new IndexedValue<T>(i, lst.get(i)));
}
}
然后使用它:
import static com.example.Itertools.enumerate;
enumerate(myList).map(i -> doSthWith(i.value, i.index));
如果你想要一个高效的LinkedList解决方案,那么你需要在不调用LinkedList.get()的情况下处理它。