如何最优雅地通过并行集合迭代?

假设我有两个平行的集合,例如:一个List<String>的人名列表,以及一个在List<Int>中的年龄列表,其顺序是相同的(所以每个集合中的任何给定索引都指向同一个人)。

我想同时遍历这两个集合,并获取每个人的名字和年龄,并用它做一些事情。 对于数组,这很容易完成:

 for (int i = 0; i < names.length; i++) { do something with names[i] .... do something with ages[i]..... } 

用集合来做这件事最优雅的方式(就可读性和速度而言)是什么?

我会创build一个封装了这两个的新对象。 把它扔到数组中并遍历它。

 List<Person> 

哪里

 public class Person { public string name; public int age; } 
 it1 = coll1.iterator(); it2 = coll2.iterator(); while(it1.hasNext() && it2.hasNext()) { value1 = it1.next(); value2 = it2.next(); do something with it1 and it2; } 

当较短的集合用尽时,该版本终止; 或者,你可以继续,直到更长的一个用尽,设置value1 resp。 value2为null。

你可以为它创build一个接口:

 public interface ZipIterator<T,U> { boolean each(T t, U u); } public class ZipUtils { public static <T,U> boolean zip(Collection<T> ct, Collection<U> cu, ZipIterator<T,U> each) { Iterator<T> it = ct.iterator(); Iterator<U> iu = cu.iterator(); while (it.hasNext() && iu.hasNext()) { if (!each.each(it.next(), iu.next()) { return false; } } return !it.hasNext() && !iu.hasNext(); } } 

然后你有:

 Collection<String> c1 = ... Collection<Long> c2 = ... zip(c1, c2, new ZipIterator<String, Long>() { public boolean each(String s, Long l) { ... } }); 
 for (int i = 0; i < names.length; ++i) { name = names.get(i); age = ages.get(i); // do your stuff } 

这并不重要。 你的代码不会得到优雅点。 只要这样做,它的工作。 请不要臃肿。

正如jeef3所build议的那样,build模真正的领域而不是保持单独的,隐式耦合的列表是正确的方式去…当这是一个选项。

有很多原因可能导致您无法采用这种方法。 如果是这样…

答:您可以使用cletusbuild议的callback方法。

B.您仍然可以select公开一个为每个组合实例公开域对象元素的迭代器。 这种方法不会强制你保持一个平行的List结构。

 private List<String> _names = ...; private List<Integer> _ages = ...; Iterator<Person> allPeople() { final Iterator<String> ni = _names.iterator(); final Iterator<Integer> ai = _ages.iterator(); return new Iterator() { public boolean hasNext() { return ni.hasNext(); } public Person next() { return new Person(ni.next(), ai.next()); } public void remove() { ni.remove(); ai.remove(); } }; } 

C.您可以使用这种变体,并使用RowSet样式游标API。 比方说, IPerson是一个描述Person的接口。 那我们可以这样做:

 public interface IPerson { String getName(); void setName(String name); ... } public interface ICursor<T> { boolean next(); T current(); } private static class PersonCursor implements IPerson, ICursor<IPerson> { private final List<String> _names; ... private int _index = -1; PersonCursor(List<String> names, List<Integer> ages) { _names = names; ... } public boolean next() { return ++_index < _names.size(); } public Person current() { return this; } public String getName() { return _names.get(_index); } public void setName(String name) { _names.set(0, name); } ... } private List<String> _names = ...; private List<Integer> _ages = ...; Cursor<Person> allPeople() { return new PersonCursor(_names, _ages); } 

请注意,B方法还可以通过引入一个Domain接口来支持列表更新,并让Iterator返回“实时”对象。

我只是在这个类似的问题中发布了这个函数(@Nils von Barth断言不是重复的)),但是它同样适用于这里:

 public static <L,R,M> List<M> zipLists( BiFunction<L,R,M> factory, Iterable<L> left, Iterable<R> right) { Iterator<L> lIter = left.iterator(); Iterator<R> rIter = right.iterator(); ImmutableList.Builder<M> builder = ImmutableList.builder(); while (lIter.hasNext() && rIter.hasNext()) { builder.add(factory.apply(lIter.next(), rIter.next())); } // Most of the existing solutions fail to enforce that the lists are the same // size. That is a *classic* source of bugs. Always enforce your invariants! checkArgument(!lIter.hasNext(), "Unexpected extra left elements: %s", ImmutableList.copyOf(lIter)); checkArgument(!rIter.hasNext(), "Unexpected extra right elements: %s", ImmutableList.copyOf(rIter)); return builder.build(); } 

然后,您可以为BiFunction提供一个工厂操作,如值types的构造函数:

 List<Person> people = zipLists(Person::new, names, ages); 

如果你真的只是想迭代它们并做一些操作,而不是构build一个新的集合,那么可以将BiFunction交换为BiFunctionBiConsumer函数返回void

我采取@cletus评论和改进它abit,这就是我使用:

 public static <T,U> void zip(Collection<T> ct, Collection<U> cu, BiConsumer<T, U> consumer) { Iterator<T> it = ct.iterator(); Iterator<U> iu = cu.iterator(); while (it.hasNext() && iu.hasNext()) { consumer.accept(it.next(), iu.next()); } } 

用法:

 zip(list1, list2, (v1, v2) -> { // Do stuff });