Saltar al contenido

¿Qué es más eficiente, un bucle para cada bucle o un iterador?

Solución:

Si simplemente está deambulando por la colección para leer todos los valores, entonces no hay diferencia entre usar un iterador o la nueva sintaxis de bucle for, ya que la nueva sintaxis solo usa el iterador bajo el agua.

Sin embargo, si por bucle se refiere al antiguo bucle “estilo c”:

for(int i=0; i<list.size(); i++) {
   Object o = list.get(i);
}

Entonces, el nuevo bucle for, o iterador, puede ser mucho más eficiente, dependiendo de la estructura de datos subyacente. La razón de esto es que para algunas estructuras de datos, get(i) es una operación O (n), que hace que el bucle sea un O (n2) operación. Una lista enlazada tradicional es un ejemplo de tal estructura de datos. Todos los iteradores tienen como requisito fundamental que next() debe ser una operación O (1), haciendo el bucle O (n).

Para verificar que el iterador se usa bajo el agua por la nueva sintaxis de bucle for, compare los códigos de bytes generados a partir de los siguientes dos fragmentos de Java. Primero el bucle for:

List<Integer>  a = new ArrayList<Integer>();
for (Integer integer : a)
{
  integer.toString();
}
// Byte code
 ALOAD 1
 INVOKEINTERFACE java/util/List.iterator()Ljava/util/Iterator;
 ASTORE 3
 GOTO L2
L3
 ALOAD 3
 INVOKEINTERFACE java/util/Iterator.next()Ljava/lang/Object;
 CHECKCAST java/lang/Integer
 ASTORE 2 
 ALOAD 2
 INVOKEVIRTUAL java/lang/Integer.toString()Ljava/lang/String;
 POP
L2
 ALOAD 3
 INVOKEINTERFACE java/util/Iterator.hasNext()Z
 IFNE L3

Y segundo, el iterador:

List<Integer>  a = new ArrayList<Integer>();
for (Iterator iterator = a.iterator(); iterator.hasNext();)
{
  Integer integer = (Integer) iterator.next();
  integer.toString();
}
// Bytecode:
 ALOAD 1
 INVOKEINTERFACE java/util/List.iterator()Ljava/util/Iterator;
 ASTORE 2
 GOTO L7
L8
 ALOAD 2
 INVOKEINTERFACE java/util/Iterator.next()Ljava/lang/Object;
 CHECKCAST java/lang/Integer
 ASTORE 3
 ALOAD 3
 INVOKEVIRTUAL java/lang/Integer.toString()Ljava/lang/String;
 POP
L7
 ALOAD 2
 INVOKEINTERFACE java/util/Iterator.hasNext()Z
 IFNE L8

Como puede ver, el código de bytes generado es efectivamente idéntico, por lo que no hay ninguna penalización de rendimiento al usar cualquiera de las formas. Por lo tanto, debe elegir la forma de bucle que le resulte más atractiva estéticamente, para la mayoría de las personas, ese será el bucle para cada bucle, ya que tiene menos código repetitivo.

La diferencia no está en el rendimiento, sino en la capacidad. Cuando usa una referencia directamente, tiene más poder sobre el uso explícito de un tipo de iterador (por ejemplo, List.iterator () frente a List.listIterator (), aunque en la mayoría de los casos devuelven la misma implementación). También tiene la capacidad de hacer referencia al iterador en su bucle. Esto le permite hacer cosas como eliminar elementos de su colección sin obtener una ConcurrentModificationException.

p.ej

Esto esta bien:

Set<Object> set = new HashSet<Object>();
// add some items to the set

Iterator<Object> setIterator = set.iterator();
while(setIterator.hasNext()){
     Object o = setIterator.next();
     if(o meets some condition){
          setIterator.remove();
     }
}

Esto no es así, ya que lanzará una excepción de modificación concurrente:

Set<Object> set = new HashSet<Object>();
// add some items to the set

for(Object o : set){
     if(o meets some condition){
          set.remove(o);
     }
}

Para ampliar la propia respuesta de Paul, ha demostrado que el código de bytes es el mismo en ese compilador en particular (¿presumiblemente el javac de Sun?) Pero los diferentes compiladores no son garantizado para generar el mismo código de bytes, ¿verdad? Para ver cuál es la diferencia real entre los dos, vayamos directamente a la fuente y verifiquemos la Especificación del lenguaje Java, específicamente 14.14.2, “La instrucción mejorada para”:

El mejorado for declaración es equivalente a un básico for declaración de la forma:

for (I #i = Expression.iterator(); #i.hasNext(); ) {
    VariableModifiers(opt) Type Identifier = #i.next();    
    Statement 
}

En otras palabras, el JLS requiere que los dos sean equivalentes. En teoría, eso podría significar diferencias marginales en el código de bytes, pero en realidad el bucle for mejorado es necesario para:

  • Invocar el .iterator() método
  • Usar .hasNext()
  • Haga que la variable local esté disponible a través de .next()

Entonces, en otras palabras, para todos los propósitos prácticos, el código de bytes será idéntico o casi idéntico. Es difícil imaginar una implementación de compilador que resulte en una diferencia significativa entre los dos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *