Control de flujo de Java: bucles para y para cada

C

Introducción

Las declaraciones condicionales y los bucles son una herramienta muy importante en la programación. No hay muchas cosas que podamos hacer con código que solo se pueda ejecutar línea por línea.

Eso es lo que significa “control de flujo”: guiar la ejecución de nuestro programa, en lugar de dejar que se ejecute línea por línea independientemente de cualquier factor interno o externo. Cada lenguaje de programación admite alguna forma de control de flujo, si no explícitamente a través de ifsy fors o declaraciones similares, entonces implícitamente nos da las herramientas para crear tales construcciones, es decir, los lenguajes de programación de bajo nivel generalmente logran ese efecto con una gran cantidad de go-to comandos.

Los bucles eran un concepto que se usaba mucho antes de que la programación de computadoras existiera, pero la primera persona en usar un bucle de software fue Ada Lovelace, comúnmente conocida por su apellido de soltera, Byron, mientras calculaba Números de Bernoulli, allá por el siglo XIX.

En Java, hay varias formas de controlar el flujo del código:

  • declaraciones if y if-else
  • cambiar declaraciones
  • declaraciones while y do-while
  • for y enhanced for bucles
  • break y continue declaraciones

El bucle for

for Los bucles se utilizan normalmente cuando el número de iteraciones es “fijo” de alguna manera. O sabemos exactamente cuántas veces se ejecutará el ciclo o tenemos una idea mejor que “hasta que n se convierta en m”.

Los bucles for se utilizan a menudo con matrices:

for (initialization; terminationCondition; update) {
    // Code here...
}

Y una implementación simple:

int[] arr = {1,2,3,4,5,6,7,8,9};

for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

Esta es una muy simple for declaración, y así es como se ejecuta:

  • Establecer la variable local i ser 0
  • Compruebe si i es menos que arr.length, si procede dentro del bloque
    2.1. Imprimir arr[i]
    2.2. Incremento i en 1, vaya al paso 2.
  • Si i no es menos que arr.length, proceda después del bloque

Tan pronto como el paso 2 encuentre que i es mayor o igual a arr.length, los bucles detienen su ejecución y Java continúa la ejecución desde la línea que sigue al bloque del bucle.

Nota: Aquí, la ubicación del operador de incremento (++) no es importante. Si nuestro paso de actualización fue ++i en cambio, nada cambiaría ya que el paso de actualización siempre se ejecuta después del código en el for bloque de bucle.

Este código, por supuesto, se imprimiría:

1
2
3
4
5
6
7
8
9

Hay tres bloques diferentes utilizados en el for bucle que nos permite hacer esto: el bloque de inicialización, la condición de terminación y el paso de actualización.

Bloque de inicialización

Un bloque de inicialización en el for El bucle se utiliza para inicializar una variable. En nuestro ejemplo, queríamos la variable i para comenzar en 0, ya que 0 es el primer índice de una matriz.

Este bloque se ejecuta solo una vez, al inicio del for lazo. También podemos definir múltiples variables del mismo tipo aquí:

// Single variable
for (int i = 0; i < 10; i++) {
    // Code
}

// Multiple variables
for (int i = 10, j = 25; i < arr.length; i++) {
    // Code
}

// Multiple variables
for (int i = 10, double j = 25.5; i < arr.length; i++) {
    // WON'T compile because we used two different types - int and double
}

Las variables inicializadas dentro del for La declaración solo se puede usar dentro del for bloquear. Acceder a ellos fuera de su alcance resultará en una excepción, como se esperaba:

for (int i = 0; i < 10; i++) {
    System.out.println(i);
}
System.out.println(i);

La variable i se ha hecho referencia fuera del alcance:

MyClass.java:6: error: cannot find symbol
System.out.println(i);
                   ^
  symbol:   variable i
  location: class MyClass
1 error

Nota: El código en el bloque de inicialización es opcional y no tiene que incluirse. Sin embargo, el bloque tiene que serlo. Por tanto, podemos escribir algo como esto:

int i = 0;
for (; i < 10; i++) {
    System.out.println(i);
}
System.out.println("ni variable is " + i);

Y daría como resultado la misma salida que si el bloque de inicialización estuviera allí, excepto que el i La variable ya no está fuera de alcance después de ejecutar la for lazo:

0
1
2
3
4
5
6
7
8
9

i variable is 10

El bloque de inicialización está técnicamente ahí, ya que incluimos el ; final de la misma, pero no hay código dentro de ella.

Condición de terminación

La condición de terminación le dice al for bucle para ejecutar el código siempre que sea true. Si la condición de terminación se evalúa como false, el paso de actualización y el resto del for se omiten el bucle. Solo puede haber una condición de terminación:

for (int i = 0; i < 10; i++) {
    System.out.println("i = " + i + " | i < 10 is true");
}

// Compiles as there's only one termination condition,
// although there's two operators - two relational and one logical
for (int i = 6; i < 10 & i > 5; i++) {
    System.out.println("i = " + i + " | i < 10 is true");
}

// WON'T compile, multiple separate termination conditions
for (int i = 0; i < 10, i > 5; i++) {
    System.out.println("i = " + i + " | i < 10 is true");
}

La salida del primer bucle:

i = 0 | i < 10 is true
i = 1 | i < 10 is true
i = 2 | i < 10 is true
i = 3 | i < 10 is true
i = 4 | i < 10 is true
i = 5 | i < 10 is true
i = 6 | i < 10 is true
i = 7 | i < 10 is true
i = 8 | i < 10 is true
i = 9 | i < 10 is true

Al llegar 10 la condición i < 10 ya no es true y el código deja de repetirse.

Y la salida del segundo bucle:

i = 6 | i < 10 is true
i = 7 | i < 10 is true
i = 8 | i < 10 is true
i = 9 | i < 10 is true

Nota: La condición de terminación también es opcional. Es válido definir un for bucle sin una condición de terminación. Sin embargo, excluir la condición de terminación hará que el código se repita infinitamente, o hasta que StackOverflowError ocurre.

for (int i = 0; ; i++) {
    System.out.println("Looping forever!");
}

Aunque no hay código en la condición de terminación, debe incluir un punto y coma para marcar que ha decidido dejarlo vacío, de lo contrario, el código no se compilará.

MyClass.java:3: error: ';' expected
    for (int i = 0; i++) {
                       ^
1 error

Paso de actualización

El paso de actualización con mayor frecuencia disminuye / incrementa algún tipo de variable de control (en nuestro caso – i) después de cada iteración del ciclo. Esencialmente, se asegura de que nuestra condición de terminación se cumpla en algún momento; en nuestro caso, se incrementa i hasta llegar a 10.

El paso de actualización solo se ejecutará si la condición de terminación se evalúa como true, y después del código en el for Se ha ejecutado el bloque de bucle. Puede tener varios pasos de actualización si lo desea, e incluso puede llamar a métodos:

for (int i = 0; i < 10; i++) {
    // Code
}

int j = 10;

for (int i = 0; i < 10; i++, j--) {
    System.out.println(i + " | " + j);
}

// Multiple variables
for (int i = 10; i < arr.length; i++, doSomething()) {
    System.out.println("Hello from the for loop");
}

public static void doSomething() {
    System.out.println("Hello from another method");
}

Y la salida de este bucle sería:

Hello from the for loop
Hello from another method!
Hello from the for loop
Hello from another method!
Hello from the for loop
Hello from another method!
Hello from the for loop
Hello from another method!

Esto también confirma que el paso de actualización se ejecuta después del código dentro del bloque.

Nota: El paso de actualización también es opcional, al igual que el bloque de inicialización y la condición de terminación. Puede ser reemplazado por código dentro del for loop, y podríamos incrementar / disminuir la variable de control antes del código de esa manera también.

¿Vacío para bucle?

Dado que los tres bloques de la for Los bucles son técnicamente opcionales, podríamos, sin ningún problema, escribir esto for lazo:

int i = 0;

// This will compile, all blocks are "present" but no code is actually there
for (;;) {
    if (i < 10)
        System.out.println(i);
    i++;
}

Si miras más de cerca, esto realmente se parece a un ciclo while:

int i = 0;

while (i < arr.length) {
    System.out.println(i);
    i++;
}

Todo lo que se puede hacer con un while El bucle se puede hacer con un for loop, y viceversa, y la elección entre ellos se decide por la legibilidad y la conveniencia.

for Los bucles y las tres partes proporcionan una descripción clara de las condiciones del bucle, dónde comienza, cómo cambia el estado y cuándo deja de iterar. Los hace muy concisos y fáciles de manipular.

Dado esto, es muy recomendable evitar cambiar la variable de control (i en nuestro caso) fuera del paréntesis después de for, a menos que sea absolutamente necesario.

Anidado para bucles

Anidado for Los bucles también son muy comunes, especialmente cuando se itera a través de matrices multidimensionales (matrices que tienen otras matrices como elementos):

int[][] multiArr = {{1,2,3},{4},{5,6}};

for (int i = 0; i < multiArr.length; i++) {
    for (int j = 0; j < multiArr[i].length; j++) {
        System.out.print(multiArr[i][j] + " ");
    }
    System.out.println();
}

Ejecutar este código produciría:

1 2 3
4
5 6

Mejorado para Loop

El mejorado foro for-each es un tipo especial de for bucle que se puede utilizar para cualquier objeto que implemente el Iterable interfaz y matrices.

Está diseñado para pasar por los elementos en orden, uno tras otro, desde el principio hasta el final. Esto difiere del tradicional for bucle en el que podríamos haber dado el paso como quisiéramos; podríamos, por ejemplo, haber impreso todos los demás elementos:

int[] arr = {1,2,3,4,5,6,7,8,9};

for (int i = 0; i < arr.length; i+=2) {
    System.out.println(arr[i]);
}

Observe que incrementamos i por 2 cada vez. Esto habría impreso lo siguiente:

1
3
5
7
9

En cambio, con for-each iteramos a través de todos los elementos usando la siguiente sintaxis:

for (ElementType localVar : somethingIterable) {
    // Code
}

El bucle almacena un elemento tras otro en el localVar variable, hasta que no queden más elementos. Fue creado para evitar hacer tradicionales for bucles que pasaron por matrices / colecciones secuencialmente. Ya no necesitamos especificar un contador, establecer una posición inicial y una posición final, indexar manualmente la matriz y ya no tenemos que preocuparnos por los límites, todo lo cual puede ser muy tedioso de escribir.

Todo esto ha sido automatizado a través del for-each.

List<String> list = new ArrayList<>(Arrays.asList("a","b","c"));

for (String s : list) {
    System.out.println(s);
}

// The traditional for syntax...
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}
// ...or...
for (Iterator<String> i = list.iterator(); i.hasNext();) {
    System.out.println(i.next());
}

El primer bucle es el más limpio y requiere menos mantenimiento por nuestra parte. Este fragmento de código podría leerse efectivamente como: “para cada cadena s en la lista de cadenas list, haz algo para s.

Bucles anidados para cada uno

for-each también admite matrices multidimensionales, una de nuestras for bucles imprimieron elementos de una matriz bidimensional: así es como se vería usando for-each:

int[][] multiArr = {{1,2,3},{4},{5,6}};

// Keep in mind that x is an array, since multiArr
// is an array of arrays
for (int[] x : multiArr) {
    for (int y : x) {
        System.out.print(y + " ");
    }
    System.out.println();
}

Del mismo modo, podemos usarlo para iterar colecciones anidadas:

ArrayList<String> stronglyTyped = new ArrayList<>();
stronglyTyped.add("Java");
stronglyTyped.add("Go");
stronglyTyped.add("Harbour");
stronglyTyped.add("Haskell");

ArrayList<String> weaklyTyped = new ArrayList<>();
weaklyTyped.add("C++");
weaklyTyped.add("C");
weaklyTyped.add("JavaScript");

ArrayList<ArrayList<String>> programmingLanguages = new ArrayList<ArrayList<String>>();
programmingLanguages.add(stronglyTyped);
programmingLanguages.add(weaklyTyped);

los programmingLanguages arraylist contiene otras dos listas de arrays – stronglyTyped y weaklyTyped.

Para atravesarlos, simplemente escribiríamos:

for (ArrayList<String> languages : programmingLanguages) {
    for (String language : languages) {
        System.out.println(language);
    }
}

La salida:

Java
Go
Harbour
Haskell
C++
C
JavaScript

Modificación de valores durante para cada uno

Es importante tener en cuenta que puede cambiar los valores de los elementos que está iterando. Por ejemplo, en el ejemplo anterior, si cambia el System.out.println(language) con System.out.println(language.toUppercase()), seríamos recibidos con:

JAVA
GO
HARBOUR
HASKELL
C++
C
JAVASCRIPT

Esto se debe a que estamos tratando con objetos. Al iterar a través de ellos, estamos asignando sus variables de referencia al language String. Llamando a cualquier cambio en el language La variable de referencia también se reflejará en la original. En el caso de las cadenas, es posible que en realidad no afecte a los objetos debido al conjunto de cadenas, pero entiendes el punto.

Esto, sin embargo, no sucede con las variables primitivas, ya que con ellas, el valor simplemente se copia en la variable a la que estamos accediendo. Entonces, cambiarlo no afectará las variables originales.

Bonus: para cada método

Si bien no es el tema central de este artículo, debemos mencionar que hay una nueva forma de recorrer las listas desde Java 8. El .forEach() ahora está disponible el método, que se puede combinar con expresiones lambda para bucles de una sola línea.

En algunos casos, esto se puede utilizar en lugar del for-each bucle, simplificando aún más la iteración:

list.forEach(x -> System.out.println(x));

Conclusión

El control de flujo en el código es esencial en casi todas las aplicaciones. Las declaraciones que alteran el flujo del código son bloques de construcción fundamentales y cada desarrollador aspirante debe tener el control total / ser consciente de cómo funcionan.

for y for-each Los bucles son buenos para ejecutar un bloque de código un número conocido de veces, a menudo con una matriz u otro tipo de iterable. También son posibles bucles más complejos, como incrementar el índice en 2, o incrementar y verificar múltiples variables.

 

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias para su correcto funcionamiento. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad