¿Cómo funciona la sentencia for mejorada para matrices y cómo obtener un iterador para una matriz?

Dado el siguiente fragmento de código:

int[] arr = {1, 2, 3}; for (int i : arr) System.out.println(i); 

Tengo las siguientes preguntas:

  1. ¿Cómo funciona el bucle for-each anterior?
  2. ¿Cómo obtengo un iterador para una matriz en Java?
  3. ¿La matriz se convierte en una lista para obtener el iterador?

Si desea un Iterator sobre una matriz, puede utilizar una de las implementaciones directas en lugar de envolver la matriz en una List . Por ejemplo:

Apache Commons Collections ArrayIterator

O bien, este, si desea usar generics:

com.Ostermiller.util.ArrayIterator

Tenga en cuenta que si desea tener un Iterator sobre tipos primitivos, no puede hacerlo, porque un tipo primitivo no puede ser un parámetro genérico. Por ejemplo, si desea un Iterator , debe usar un Iterator lugar, lo que dará como resultado mucho autoboxing y -unboxing si está respaldado por un int[] .

No, no hay conversión La JVM simplemente itera sobre la matriz usando un índice en el fondo.

Cita de Effective Java 2nd Ed., Artículo 46:

Tenga en cuenta que no hay penalización de rendimiento para usar el bucle for-each, incluso para matrices. De hecho, puede ofrecer una ligera ventaja de rendimiento sobre un bucle ordinario en algunas circunstancias, ya que calcula el límite del índice de matriz solo una vez.

Por lo tanto, no puede obtener un Iterator para una matriz (a menos, por supuesto, convirtiéndolo primero en una List ).

Arrays.asList (arr) .iterator ();

O escribe el tuyo, implementando la interfaz de ListIterator …

La colección de Google Guava Librarie proporciona tal función:

 Iterator it = Iterators.forArray(array); 

Uno debería preferir la guayaba a la colección Apache (que parece estar abandonada).

En Java 8:

 Arrays.stream(arr).iterator(); 
 public class ArrayIterator implements Iterator { private T array[]; private int pos = 0; public ArrayIterator(T anArray[]) { array = anArray; } public boolean hasNext() { return pos < array.length; } public T next() throws NoSuchElementException { if (hasNext()) return array[pos++]; else throw new NoSuchElementException(); } public void remove() { throw new UnsupportedOperationException(); } } 

Estrictamente hablando, no se puede obtener un iterador de la matriz primitiva, porque Iterator.next () solo puede devolver un Objeto. Pero a través de la magia del autoboxing, puede obtener el iterador usando el método Arrays.asList () .

 Iterator it = Arrays.asList(arr).iterator(); 

La respuesta anterior es incorrecta, no puede usar Arrays.asList() en una matriz primitiva, devolvería una List . Use en su lugar Ints.asList() Guava .

No se puede obtener directamente un iterador para una matriz.

Pero puede usar una Lista, respaldada por su matriz, y obtener un ierator en esta lista. Para eso, su matriz debe ser una matriz Integer (en lugar de una matriz int):

 Integer[] arr={1,2,3}; List arrAsList = Arrays.asList(arr); Iterator iter = arrAsList.iterator(); 

Nota: es solo teoría. Puede obtener un iterador como este, pero lo desanimo a hacerlo. Las interpretaciones no son buenas en comparación con una iteración directa en la matriz con la “syntax extendida”.

Nota 2: una construcción de lista con este método no admite todos los métodos (ya que la lista está respaldada por la matriz que tiene un tamaño fijo). Por ejemplo, el método “eliminar” de su iterador dará como resultado una excepción.

¿Cómo funciona el bucle for-each anterior?

Al igual que muchas otras características de matriz, JSL menciona las matrices de forma explícita y les otorga propiedades mágicas. JLS 7 14.14.2 :

 EnhancedForStatement: for ( FormalParameter : Expression ) Statement 

[…]

Si el tipo de expresión es un subtipo de Iterable , la traducción es la siguiente

[…]

De lo contrario, la Expresión necesariamente tiene un tipo de matriz, T[] . [[ ¡MAGIA! ]]

Deje que L1 ... Lm sea ​​la secuencia (posiblemente vacía) de las tags que preceden inmediatamente a la statement for mejorada.

El enunciado mejorado para es equivalente a una statement básica para el formulario:

 T[] #a = Expression; L1: L2: ... Lm: for (int #i = 0; #i < #a.length; #i++) { VariableModifiersopt TargetType Identifier = #a[#i]; Statement } 

#a y #i son identificadores generados automáticamente que son distintos de cualquier otro identificador (generado automáticamente o no) que están en el scope en el punto donde se produce la instrucción for forzada.

¿La matriz se convierte en una lista para obtener el iterador?

Vamos a javap :

 public class ArrayForLoop { public static void main(String[] args) { int[] arr = {1, 2, 3}; for (int i : arr) System.out.println(i); } } 

entonces:

 javac ArrayForLoop.java javap -v ArrayForLoop 

método main con un poco de edición para que sea más fácil de leer:

  0: iconst_3 1: newarray int 3: dup 4: iconst_0 5: iconst_1 6: iastore 7: dup 8: iconst_1 9: iconst_2 10: iastore 11: dup 12: iconst_2 13: iconst_3 14: iastore 15: astore_1 16: aload_1 17: astore_2 18: aload_2 19: arraylength 20: istore_3 21: iconst_0 22: istore 4 24: iload 4 26: iload_3 27: if_icmpge 50 30: aload_2 31: iload 4 33: iaload 34: istore 5 36: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 39: iload 5 41: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 44: iinc 4, 1 47: goto 24 50: return 

Descompostura:

  • 0 a 14 : crea la matriz
  • 15 a 22 : prepárate para el ciclo for. En 22 , almacene el entero 0 de la stack en la posición local 4 . ESA es la variable de bucle.
  • 24 a 47 : el bucle. La variable de bucle se recupera en 31 , y se incrementa en 44 . Cuando es igual a la longitud de la matriz que se almacena en la variable local 3 en la verificación en 27 , el ciclo finaliza.

Conclusión : es lo mismo que hacer un ciclo for explícito con una variable de índice, sin iteractors implicados.

Para (2), Guava proporciona exactamente lo que desea como Int.asList () . Hay un equivalente para cada tipo primitivo en la clase asociada, por ejemplo, Booleans para boolean , etc.

  int[] arr={1,2,3}; for(Integer i : Ints.asList(arr)) { System.out.println(i); } 

Llego un poco tarde al juego, pero noté algunos puntos clave que quedaron fuera, particularmente en relación con Java 8 y la eficacia de Arrays.asList .

1. ¿Cómo funciona el bucle for-each?

Como señaló Ciro Santilli, 事件 法轮功 轩 轩 轩, señaló, hay una útil utilidad para examinar el bytecode que se envía con el JDK: javap . Usando eso, podemos determinar que los siguientes dos fragmentos de código produzcan un código de bytes idéntico a partir de Java 8u74:

Para cada bucle:

 int[] arr = {1, 2, 3}; for (int n : arr) { System.out.println(n); } 

En bucle:

 int[] arr = {1, 2, 3}; { // These extra braces are to limit scope; they do not affect the bytecode int[] iter = arr; int length = iter.length; for (int i = 0; i < length; i++) { int n = iter[i]; System.out.println(n); } } 

2. ¿Cómo obtengo un iterador para una matriz en Java?

Si bien esto no funciona para las primitivas, se debe tener en cuenta que la conversión de una matriz a una Lista con Arrays.asList no afecta el rendimiento de ninguna manera significativa. El impacto en la memoria y el rendimiento es casi inconmensurable.

Arrays.asList no utiliza una implementación de lista normal que sea fácilmente accesible como clase. Utiliza java.util.Arrays.ArrayList , que no es lo mismo que java.util.ArrayList . Es un envoltorio muy delgado alrededor de una matriz y no puede redimensionarse. Al observar el código fuente de java.util.Arrays.ArrayList , podemos ver que está diseñado para ser funcionalmente equivalente a una matriz. Casi no hay gastos generales. Tenga en cuenta que he omitido todo menos el código más relevante y he añadido mis propios comentarios.

 public class Arrays { public static  List asList(T... a) { return new ArrayList<>(a); } private static class ArrayList extends AbstractList implements RandomAccess, java.io.Serializable { private final E[] a; ArrayList(E[] array) { a = Objects.requireNonNull(array); } @Override public int size() { return a.length; } @Override public E get(int index) { return a[index]; } @Override public E set(int index, E element) { E oldValue = a[index]; a[index] = element; return oldValue; } } } 

El iterador está en java.util.AbstractList.Itr . En cuanto a iteradores, es muy simple; simplemente llama a get() hasta que se alcanza el size() , como lo haría un ciclo de manual for. Es la implementación más simple y generalmente más eficiente de un Iterator para una matriz.

De nuevo, Arrays.asList no crea un java.util.ArrayList . Es mucho más liviano y adecuado para obtener un iterador con una sobrecarga insignificante.

Matrices primitivas

Como otros han notado, Arrays.asList no se puede usar en matrices primitivas. Java 8 presenta varias tecnologías nuevas para tratar con colecciones de datos, varias de las cuales podrían usarse para extraer iteradores simples y relativamente eficientes de las matrices. Tenga en cuenta que si usa generics, siempre tendrá el problema del box-unboxing: necesitará convertir de int a Integer y luego volver a int. Si bien el boxeo / unboxing suele ser insignificante, sí tiene un impacto en el rendimiento de O (1) en este caso y podría generar problemas con arreglos muy grandes o en computadoras con recursos muy limitados (es decir, SoC ).

Mi favorito personal para cualquier tipo de operación de casting / boxing en Java 8 es la nueva API de transmisión. Por ejemplo:

 int[] arr = {1, 2, 3}; Iterator iterator = Arrays.stream(arr).mapToObj(Integer::valueOf).iterator(); 

La API de secuencias también ofrece construcciones para evitar el problema del boxeo en primer lugar, pero esto requiere abandonar los iteradores a favor de las secuencias. Existen tipos de transmisión dedicados para int, long y double (IntStream, LongStream y DoubleStream, respectivamente).

 int[] arr = {1, 2, 3}; IntStream stream = Arrays.stream(arr); stream.forEach(System.out::println); 

Curiosamente, Java 8 también agrega java.util.PrimitiveIterator . Esto proporciona lo mejor de ambos mundos: compatibilidad con Iterator través del boxeo junto con métodos para evitar el boxeo. PrimitiveIterator tiene tres interfaces integradas que lo amplían: OfInt, OfLong y OfDouble. Los tres nextInt() si se llama a next() pero también pueden devolver primitivas a través de métodos como nextInt() . El código más nuevo diseñado para Java 8 debe evitar el uso de next() menos que el boxeo sea absolutamente necesario.

 int[] arr = {1, 2, 3}; PrimitiveIterator.OfInt iterator = Arrays.stream(arr); // You can use it as an Iterator without casting: Iterator example = iterator; // You can obtain primitives while iterating without ever boxing/unboxing: while (iterator.hasNext()) { // Would result in boxing + unboxing: //int n = iterator.next(); // No boxing/unboxing: int n = iterator.nextInt(); System.out.println(n); } 

Si aún no está en Java 8, lamentablemente su opción más simple es mucho menos conciso y seguramente involucrará el boxeo:

 final int[] arr = {1, 2, 3}; Iterator iterator = new Iterator() { int i = 0; @Override public boolean hasNext() { return i < arr.length; } @Override public Integer next() { if (!hasNext()) { throw new NoSuchElementException(); } return arr[i++]; } }; 

O si quieres crear algo más reutilizable:

 public final class IntIterator implements Iterator { private final int[] arr; private int i = 0; public IntIterator(int[] arr) { this.arr = arr; } @Override public boolean hasNext() { return i < arr.length; } @Override public Integer next() { if (!hasNext()) { throw new NoSuchElementException(); } return arr[i++]; } } 

Podría solucionar el problema del boxeo aquí agregando sus propios métodos para obtener primitivos, pero solo funcionaría con su propio código interno.

3. ¿La matriz se convierte en una lista para obtener el iterador?

No, no es. Sin embargo, eso no significa que envolverlo en una lista le dará un peor rendimiento, siempre que use algo ligero como Arrays.asList .

Soy un estudiante reciente, pero CREO que el ejemplo original con int [] itera sobre el conjunto de primitivas, pero no mediante el uso de un objeto iterador. Simplemente tiene la misma syntax (similar) con diferentes contenidos,

 for (primitive_type : array) { } for (object_type : iterableObject) { } 

Arrays.asList () APPARENTEMENTE solo aplica los métodos de Lista a una matriz de objetos que se le da, pero para cualquier otro tipo de objeto, incluyendo una matriz primitiva, iterator (). Next () APARENTEMENTE simplemente le entrega la referencia al objeto original, tratando como una lista con un elemento. ¿Podemos ver el código fuente para esto? ¿No preferirías una excepción? No importa. Supongo (es GUESS) que es como (o ES) una Colección singleton. Así que aquí asList () es irrelevante para el caso con una matriz de primitivas, pero confusa. NO SÉ que tengo razón, pero escribí un progtwig que dice que lo soy.

Por lo tanto, este ejemplo (donde básicamente asList () no hace lo que pensaste que sería, y por lo tanto no es algo que usarías de esta manera) – Espero que el código funcione mejor que mi marcado como código, y hey, mira esa última línea:

 // Java(TM) SE Runtime Environment (build 1.6.0_19-b04) import java.util.*; public class Page0434Ex00Ver07 { public static void main(String[] args) { int[] ii = new int[4]; ii[0] = 2; ii[1] = 3; ii[2] = 5; ii[3] = 7; Arrays.asList(ii); Iterator ai = Arrays.asList(ii).iterator(); int[] i2 = (int[]) ai.next(); for (int i : i2) { System.out.println(i); } System.out.println(Arrays.asList(12345678).iterator().next()); } } 

Me gusta la respuesta desde la trigésima usando Iterators de Guava. Sin embargo, en algunos frameworks obtengo null en lugar de una matriz vacía, y Iterators.forArray(array) no maneja bien. Así que se me ocurrió este método de ayuda, al que puedes llamar con Iterator it = emptyIfNull(array);

 public static  UnmodifiableIterator emptyIfNull(F[] array) { if (array != null) { return Iterators.forArray(array); } return new UnmodifiableIterator() { public boolean hasNext() { return false; } public F next() { return null; } }; }