java - objetos - ¿Cómo funciona la sentencia for mejorada para matrices y cómo obtener un iterador para una matriz?
for loop java español (13)
Dado el siguiente fragmento de código:
int[] arr = {1, 2, 3};
for (int i : arr)
System.out.println(i);
Tengo las siguientes preguntas:
- ¿Cómo funciona el bucle for-each anterior?
- ¿Cómo obtengo un iterador para una matriz en Java?
- ¿La matriz se convierte en una lista para obtener el iterador?
¿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 etiquetas que preceden inmediatamente a la declaración for mejorada.El enunciado mejorado para es equivalente a una declaración 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 alcance 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
a14
: crea la matriz -
15
a22
: prepárate para el ciclo for. En 22 , almacene el entero0
de la pila en la posición local4
. ESA es la variable de bucle. -
24
a47
: el bucle. La variable de bucle se recupera en31
, y se incrementa en44
. Cuando es igual a la longitud de la matriz que se almacena en la variable local 3 en la verificación en27
, el ciclo finaliza.
Conclusión : es lo mismo que hacer un ciclo for explícito con una variable de índice, sin iteractors implicados.
En Java 8:
Arrays.stream(arr).iterator();
La colección de Google Guava Librarie proporciona tal función:
Iterator<String> it = Iterators.forArray(array);
Uno debería preferir la guayaba a la colección Apache (que parece estar abandonada).
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 <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
private static class ArrayList<E> extends AbstractList<E> 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 genéricos, 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<Integer> 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<T>
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<Integer> without casting:
Iterator<Integer> 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<Integer> iterator = new Iterator<Integer>() {
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<Integer> {
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
.
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<String> it = emptyIfNull(array);
public static <F> UnmodifiableIterator<F> emptyIfNull(F[] array) {
if (array != null) {
return Iterators.forArray(array);
}
return new UnmodifiableIterator<F>() {
public boolean hasNext() {
return false;
}
public F next() {
return null;
}
};
}
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<Integer> arrAsList = Arrays.asList(arr);
Iterator<Integer> 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 "sintaxis 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.
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
).
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);
}
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 genéricos:
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<int>
, debe usar un Iterator<Integer>
lugar, lo que dará como resultado mucho autoboxing y -unboxing si está respaldado por un int[]
.
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 sintaxis (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 programa 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());
}
}
Arrays.asList (arr) .iterator ();
O escribe el tuyo, implementando la interfaz de ListIterator ...
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<Integer> it = Arrays.asList(arr).iterator();
La respuesta anterior es incorrecta, no puede usar Arrays.asList()
en una matriz primitiva, devolvería una List<int[]>
. Use en su lugar Ints.asList()
Guava .
public class ArrayIterator<T> implements Iterator<T> {
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();
}
}