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 if
sy for
s 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
yenhanced for
buclesbreak
ycontinue
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 quearr.length
, si procede dentro del bloque
2.1. Imprimirarr[i]
2.2. Incrementoi
en 1, vaya al paso 2. - Si
i
no es menos quearr.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 for
o 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.