Siendo algo nuevo en el lenguaje Java, estoy tratando de familiarizarme con todas las formas (o al menos las no patológicas) en que uno puede iterar a través de una lista (o quizás otras colecciones) y las ventajas o desventajas de cada una.
Dado un objeto List
, conozco las siguientes formas de iterar a través de todos los elementos:
while
/ do while
)// Not recommended (see below)!
for (int i = 0; i < list.size(); i++) {
E element = list.get(i);
// 1 - can call methods of element
// 2 - can use 'i' to make index-based calls to methods of list
// ...
}
Nota: Como señaló @amarseillan, esta forma es una mala elección
para iterar sobre List
s, porque la implementación real del método
método get
puede no ser tan eficiente como cuando se utiliza un Iterator
.
Por ejemplo, las implementaciones de LinkedList
deben recorrer todos los
los elementos anteriores a i para obtener el elemento i-ésimo.
En el ejemplo anterior no hay forma de que la implementación de List
pueda
guarde su lugar para que las futuras iteraciones sean más eficientes.
En el caso de un ArrayList
no importa realmente, porque la complejidad/coste de get
es de tiempo constante (O(1)) mientras que para un LinkedList
es proporcional al tamaño de la lista (O(n)).
Para más información sobre la complejidad computacional de las implementaciones de Collections
incorporadas, consulta esta pregunta.
for (E element : list) {
// 1 - can call methods of element
// ...
}
for (Iterator<E> iter = list.iterator(); iter.hasNext(); ) {
E element = iter.next();
// 1 - can call methods of element
// 2 - can use iter.remove() to remove the current element from the list
// ...
}
for (ListIterator<E> iter = list.listIterator(); iter.hasNext(); ) {
E element = iter.next();
// 1 - can call methods of element
// 2 - can use iter.remove() to remove the current element from the list
// 3 - can use iter.add(...) to insert a new element into the list
// between element and iter->next()
// 4 - can use iter.set(...) to replace the current element
// ...
}
list.stream().map(e -> e + 1); // Can apply a transformation function for e
(Un método map de la API Stream de Java 8's (ver la respuesta de @i_am_zero').
En Java 8 las clases de colección que implementan Iterable
(por ejemplo, todas las Listas
) tienen ahora un método forEach
, que puede ser utilizado en lugar de la [declaración de bucle for][for-loop] demostrada anteriormente. (Aquí hay [otra pregunta][java-8-foreach-comparison] que proporciona una buena comparación).
Arrays.asList(1,2,3,4).forEach(System.out::println);
// 1 - can call methods of an element
// 2 - would need reference to containing object to remove an item
// (TODO: someone please confirm / deny this)
// 3 - functionally separates iteration from the action
// being performed with each item.
Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
// Same capabilities as above plus potentially greater
// utilization of parallelism
// (caution: consequently, order of execution is not guaranteed,
// see [Stream.forEachOrdered][stream-foreach-ordered] for more
// information about this).
¿Qué otras formas hay, si es que hay alguna?
(BTW, mi interés no proviene en absoluto de un deseo de [optimizar el rendimiento][iterator-performance-question]; sólo quiero saber qué formas están disponibles para mí como desarrollador).
Ejemplo de cada tipo enumerado en la pregunta:
import java.util.*;
public class ListIterationExample {
public static void main(String []args){
List<Integer> numbers = new ArrayList<Integer>();
// populates list with initial values
for (Integer i : Arrays.asList(0,1,2,3,4,5,6,7))
numbers.add(i);
printList(numbers); // 0,1,2,3,4,5,6,7
// replaces each element with twice its value
for (int index=0; index < numbers.size(); index++) {
numbers.set(index, numbers.get(index)*2);
}
printList(numbers); // 0,2,4,6,8,10,12,14
// does nothing because list is not being changed
for (Integer number : numbers) {
number++; // number = new Integer(number+1);
}
printList(numbers); // 0,2,4,6,8,10,12,14
// same as above -- just different syntax
for (Iterator<Integer> iter = numbers.iterator(); iter.hasNext(); ) {
Integer number = iter.next();
number++;
}
printList(numbers); // 0,2,4,6,8,10,12,14
// ListIterator<?> provides an "add" method to insert elements
// between the current element and the cursor
for (ListIterator<Integer> iter = numbers.listIterator(); iter.hasNext(); ) {
Integer number = iter.next();
iter.add(number+1); // insert a number right before this
}
printList(numbers); // 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
// Iterator<?> provides a "remove" method to delete elements
// between the current element and the cursor
for (Iterator<Integer> iter = numbers.iterator(); iter.hasNext(); ) {
Integer number = iter.next();
if (number % 2 == 0) // if number is even
iter.remove(); // remove it from the collection
}
printList(numbers); // 1,3,5,7,9,11,13,15
// ListIterator<?> provides a "set" method to replace elements
for (ListIterator<Integer> iter = numbers.listIterator(); iter.hasNext(); ) {
Integer number = iter.next();
iter.set(number/2); // divide each element by 2
}
printList(numbers); // 0,1,2,3,4,5,6,7
}
public static void printList(List<Integer> numbers) {
StringBuilder sb = new StringBuilder();
for (Integer number : numbers) {
sb.append(number);
sb.append(",");
}
sb.deleteCharAt(sb.length()-1); // remove trailing comma
System.out.println(sb.toString());
}
}
No sé lo que considera patológico, pero permítame ofrecerle algunas alternativas que podría no haber visto antes:
List<E> sl= list ;
while( ! sl.empty() ) {
E element= sl.get(0) ;
.....
sl= sl.subList(1,sl.size());
}
O su versión recursiva:
void visit(List<E> list) {
if( list.isEmpty() ) return;
E element= list.get(0) ;
....
visit(list.subList(1,list.size()));
}
También, una versión recursiva del clásico for(int i=0...
:
void visit(List<E> list,int pos) {
if( pos >= list.size() ) return;
E element= list.get(pos) ;
....
visit(list,pos+1);
}
Los menciono porque eres "algo nuevo en Java" y esto podría ser interesante.
Siempre puedes cambiar el primer y tercer ejemplo por un bucle while y un poco más de código. Esto te da la ventaja de poder utilizar el do-while:
int i = 0;
do{
E element = list.get(i);
i++;
}
while (i < list.size());
Por supuesto, este tipo de cosas puede causar una NullPointerException si el list.size() devuelve 0, porque siempre se ejecuta al menos una vez. Esto se puede arreglar probando si el elemento es nulo antes de usar sus atributos / métodos. Aun así, es mucho más sencillo y fácil utilizar el bucle for