¿Cómo iterar más elegantemente a través de colecciones paralelas?

Digamos que tengo 2 colecciones paralelas, por ejemplo: una lista de nombres de personas en una List y una lista de su edad en una List en el mismo orden (para que cualquier índice dado en cada colección se refiera a la misma persona )

Quiero iterar a través de ambas colecciones al mismo tiempo y buscar el nombre y la edad de cada persona y hacer algo con ella. Con arreglos esto se hace fácilmente con:

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

¿Cuál sería la forma más elegante (en términos de legibilidad y velocidad) de hacer esto con las colecciones?

Crearía un nuevo objeto que encapsula los dos. Tíralo en la matriz e itera sobre eso.

 List 

Dónde

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

Esta versión finaliza cuando se agota la colección más corta; alternativamente, puede continuar hasta que el más largo se agote, estableciendo value1 resp. value2 a null.

Podría crear una interfaz para ello:

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

Y luego tienes:

 Collection c1 = ... Collection c2 = ... zip(c1, c2, new ZipIterator() { 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 } 

Realmente no importa. Su código no obtendrá puntos por elegancia. Solo hazlo para que funcione. Y por favor no hinches.

Tomé el comentario de @cletus y lo mejoré, y eso es lo que uso:

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

Uso:

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

Si bien las soluciones presentadas son correctas, prefiero la siguiente porque sigue las guías del elemento efectivo de Java 57: minimizar el scope de las variables locales:

  for (Iterator i = lst1.iterator(), ii = lst2.iterator(); i.hasNext() && ii.hasNext(); ) { String e1 = i.next(); String e2 = ii.next(); .... } 

Según lo sugerido por jeef3, modelar el dominio verdadero en lugar de mantener Listas separadas, implícitamente acopladas es el camino correcto a seguir … cuando esta es una opción.

Existen varios motivos por los cuales es posible que no pueda adoptar este enfoque. Si es así…

R. Puedes usar un enfoque de callback, como lo sugirió cletus.

B. Todavía puede optar por exponer un iterador que expone el elemento de objeto de dominio para cada instancia compuesta. Este enfoque no te obliga a mantener una estructura de listas paralelas.

 private List _names = ...; private List _ages = ...; Iterator allPeople() { final Iterator ni = _names.iterator(); final Iterator 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. Puede usar una variación de esto y usar una API de cursor de estilo RowSet. Digamos que IPerson es una interfaz que describe Persona. Entonces podemos hacer:

 public interface IPerson { String getName(); void setName(String name); ... } public interface ICursor { boolean next(); T current(); } private static class PersonCursor implements IPerson, ICursor { private final List _names; ... private int _index = -1; PersonCursor(List names, List 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 _names = ...; private List _ages = ...; Cursor allPeople() { return new PersonCursor(_names, _ages); } 

Tenga en cuenta que el enfoque B también se debe hacer para admitir actualizaciones a la lista introduciendo una interfaz de Dominio, y haciendo que el iterador devuelva objetos “activos”.

Acabo de publicar esta función en esta pregunta similar (que @Nils von Barth afirma no es un duplicado;)), pero es igualmente aplicable aquí:

 public static  List zipLists( BiFunction factory, Iterable left, Iterable right) { Iterator lIter = left.iterator(); Iterator rIter = right.iterator(); ImmutableList.Builder 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(); } 

A continuación, puede proporcionar una operación de fábrica para BiFunction , como el constructor de un tipo de valor:

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

Si realmente desea iterar sobre ellos y realizar alguna operación, en lugar de construir una nueva colección, puede cambiar la BiFunction para un BiConsumer y hacer que la función vuelva a estar void .