Skip to content

Operaciones y Sentencias

4. Operadores y sentencias

4.1. Entender los operadores de Java

Los operadores de java son símbolos especiales que se aplican un grupo de variables, valores o “literales” y que devuelven un valor. Existen tres tipos de operadores en Java: unario, binario y ternario. Estos tipos de operadores pueden ser aplicados a uno, dos o tres operandos. Los operadores de java no siempre se evalúan de izquierda a derecha. En el siguiente ejemplo se puede observar como la expresión se evalúa de derecha a izquierda:

int y = 4; double x =
3 + 2 * --y;
En el ejemplo primero se reduce en uno la variable y, después se multiplica por 2 y por último se le suman 3. El resultado final será convertido de 9 a 9.0 y asignado a la variable x. Los valores de x e y serán 9.0 y 3. A menos que estén entre paréntesis, los operadores de Java siguen un orden de operación que se encuentran listados en la tabla. Si dos operadores están en el mismo nivel se evaluarán de izquierda a derecha.

Operador Símbolos y ejemplos
Operadores post-unarios Expresión++, Expresión--
Operadores pre-unarios ++Expresión, --Expresión
Operadores unarios +, -,!
Multiplicación, División, Módulo *, /, %
Suma, Resta +, -
Operadores de cambio <<, >>, >>>
Operadores relacionales <, >, <=, >=, instanceof
Igual, Distinto ==, !=
Operadores lógicos &, ^,
Operadores lógicos de cortocircuito &&,
Operadores ternarios Expresión booleana? expresión1 : expresión 2
Operadores de asignación =, +=, -=, *=, /=, %=, &=, ^=, !=, <<=, >>=, >>>=

4.2. Trabajando con operadores binarios aritméticos

4.2.1. Operadores aritméticos

Los operadores aritméticos son los utilizados en las matemáticas y son los siguiente: la suma (+), la resta (-), la multiplicación (*), la división (/) y el módulo (%). También se incluyen los operadores unarios

++ y --. Como puedes comprobar en la tabla anterior los operadores , / y % tienen mayor orden de preferencia que los operadores + y -, por lo que la siguiente expresión:

int x = 2 * 5 + 3 * 4 – 8;
Se evaluará primero 25 y 3*4 simplificando la expresión a la siguiente:
int x = 10 + 12 – 8;
Después se evaluará la expresión de izquierda a derecha dando como resultado 14. Se puede cambiar el orden de la operación añadiendo paréntesis a las secciones que se quiere evaluar primero. Se compara el ejemplo de antes con el siguiente que incorpora paréntesis:
int x = 2 * ((5 + 3) * 4 – 8);
Esta vez se deberá evaluar la suma 5 + 3 primero, que reducirá la expresión a:
int x = 2 * (8 * 4 - 8);
Se podrá reducir la expresión multiplicando los dos primeros valores dentro del paréntesis:
int x = 2 * (32 - 8);
Después se restará los valores de dentro del paréntesis antes de multiplicarlos por el valor de fuera:
int x = 2 * 24;
Finalmente se multiplicarán los valores restantes, dando un resultado de 48. Todos los operadores aritméticos pueden ser aplicados a cualquier tipo primitivo de Java, salvo los tipos boolean y string. Además, solo los operadores + y += pueden ser aplicados a variables string, lo que resulta ser una concatenación de string. El módulo es el resto de la división de dos números, por ejemplo 9 divido entre 3 no tiene resto por lo que 9 % 3 será 0. Por otra parte 11 divido entre 3 si tiene resto por lo tanto 11 % 3 será igual a 2. Hay que tener clara la diferencia entre la división y el módulo. La división devuelve el cociente, mientras que el módulo devuelve el resto de la división. Los siguientes ejemplos ilustran la diferencia entre ambas operaciones:
System.out.print(9 / 3); // Outputs 3
System.out.print(9 % 3); // Outputs 0
System.out.print(10 / 3); // Outputs 3
System.out.print(10 % 3); // Outputs 1
System.out.print(11 / 3); // Outputs 3
System.out.print(11 % 3); // Outputs 2
System.out.print(12 / 3); // Outputs 4
System.out.print(12 % 3); // Outputs 0
Hay que tener en cuenta que el resultado de la división solo se incrementa cuando el valor de la izquierda se incrementa de 9 a 12, mientras que el módulo incrementa en 1, cada vez que el valor de la izquierda se incrementa, hasta que se convierte en 0. Para un divisor dado y, que es 3 en estos ejemplos, la

operación de módulo da como resultado un valor entre 0 y (y - 1) para dividendos positivos. Esto significa que el resultado de una operación de módulo es siempre 0,1 o 2. El funcionamiento del módulo no se limita a los valores enteros positivos en Java y también puede aplicarse a números enteros negativos y números enteros de coma flotante. Para un divisor “y” y dividendo negativo dado, el valor del módulo resultante está entre (-y + 1) y 0.

4.2.2. Promoción numérica

Reglas de la promoción numérica:

  1. Si dos variables tienes diferente tipo, Java automáticamente convertirá una de las variables al tipo más grande de las dos.
  2. Si una de las variables es Integer y la otra de coma flotante(float), Java automáticamente convertirá la variable Integer en una de coma flotante.
  3. Los tipos de datos pequeños como byte, short y char son convertidos a Int siempre que son usados por un operador aritmético binario, incluso si ninguno de los operandos es un Int.
  4. Después de que se hayan convertido las variables y los operandos tengan el mismo tipo, el resultado se guardará con el mismo tipo al que se hayan convertido los operandos. Las dos últimas reglas son aquellas con las que la mayoría de la gente tiene problemas. Por lo que respecta a la tercera regla, debe tenerse en cuenta que los operadores unitarios están excluidos de esta. Por ejemplo, aplicar ++ a un valor short dará como resultado un valor short.

4.3. Trabajando con operadores unarios

Por definición un operador unario es uno que requiere exactamente un operando o variable para funcionar. Como se muestra en la tabla suelen realizar tareas simples como incrementar en uno una variable o negar el valor de un boolean.

Operador unario Descripción
+ Indica que un número es positivo, aunque se supone que los números son positivos en Java a menos que vayan acompañados de un operador negativo unario.
- Indica que un número es negativo o niega una expresión
++ Incremento una variable en 1
-- Decremento de una variable en 1
! Invierte el valor lógico de un boolean

4.3.1. Complemento lógico y operadores de negación

El operador de complemento lógico(!) invierte el valor de una expresión booleana. Por ejemplo, si el valor es true, se invertiría a false, y viceversa. Para demostrar esto, se comparará la salida de las siguientes instrucciones:

boolean x = false;
System.out.printl(x); //false x = !x;
System.out.println(x); //true
Por otra parte, el operador de negación (-) cambia el signo de una expresión numérica como se muestra a continuación:
double x =

1.21;
System.out.println(x); //1.21 x = -x;
System.out.println(x); //-1.21 x = -x;
System.out.println(x); //1.21
Si uno se basa en la descripción, podría ser obvio que algunos operadores requieran la variable de un tipo específico. No se puede utilizar un operador de negación, -, con una expresión booleana, ni se puede aplicar un complemento lógico a una expresión numérica. Por ejemplo, ninguna de las siguientes líneas de código compilará:
int x = !5; // DOES NOT COMPILE boolean
y = -true; // DOES NOT COMPILE boolean
z = !0; // DOES NOT COMPILE
La primera declaración no se compilará debido al hecho de que en Java no se puede realizar una inversión lógica de un valor numérico. La segunda declaración no compilará porque no se puede negar numéricamente un valor booleano, es necesario utilizar el operador lógico inverso. Finalmente, la última sentencia no se compila porque no se puede tomar el complemento lógico de un valor numérico, ni se puede asignar un entero a una variable booleana.

4.3.2. Operadores de incremento y decremento.

Los operadores de incremento y decremento (++, --) pueden aplicarse a operandos numéricos y tienen mayor preferencia que los operadores binarios. Los operadores de incremento y decremento requieren un cuidado especial ya que el orden en el que son utilizados con los operandos pueden provocar un procesamiento distinto de la expresión. Si el operador está situado delante del operando entonces el operador se aplica primero y luego se devuelve el valor. Por otro lado, si el operador se encuentra detrás del operando se devuelve primero el valor original y luego se aplica el operador. El siguiente código ilustra las diferencias:

int counter = 0;

System.out.println(counter); // Outputs 0

System.out.println(++counter); // Outputs 1

System.out.println(counter); // Outputs 1

System.out.println(counter--); // Outputs 1

System.out.println(counter); // Outputs 0
El primer operador de pre-incremento actualiza el valor del contador y devuelve el nuevo valor de 1. El siguiente operador de post-decremento también actualiza el valor del contador pero devuelve el valor antes de que se produzca la disminución. El siguiente ejemplo es todavía más complicado ya que se modifica el valor 3 veces en la misma línea:
int x = 3;
int y = ++x * 5 / x-- + --x; 
System.out.println("x is " + x);
System.out.println("y is " + y);
Cada vez que se modifica, la expresión pasa de izquierda a derecha, el valor de x cambia, con diferentes valores asignados a la variable. Como recordarás de nuestra discusión sobre la precedencia del operador, el orden de la operación juega un papel importante en evaluando este ejemplo. Primero, la x se incrementa y se devuelve a la expresión, que se multiplica por 5:
int y = 4 * 5 / x-- + --x; // x con valor 4
A continuación, se decrementa x, pero el valor original de 4 se utiliza en la expresión, lo que lleva a esto:
int y = 4 * 5 / 4 + --x; // x con valor 3
La asignación final de x reduce el valor a 2, y como se trata de un operador pre-incremental, se devuelve ese valor:
int y = 4 * 5 / 4 + 2; // x con valor 2
Finalmente, se realiza la multiplicación y a continuación la división, y por último la suma dando como resultado:
x is 2 y
is 7

4.4. Usando los operadores binarios adicionales

4.4.1. Operadores de asignación

Los operadores de asignación son operadores binarios que modifican la variable con el valor del lado derecho de la ecuación. El operador de asignación más simple es el operador “=”:

int x = 1;
Esta declaración asigna a x el valor 1. Java transformará automáticamente de tipos de datos pequeños a grandes, como vimos en la sección anterior, pero lanzará una excepción de compilador si detecta que está intentando convertir tipos de datos grandes a pequeños. Vamos a ver unos ejemplos para demostrar como el “casting” puede resolver estos problemas:
int x = 1.0; // DOES NOT COMPILE 

short y = 1921222; // DOES NOT

COMPILE int z = 9f; // DOES NOT COMPILE

long t = 192301398193810323; // DOES NOT COMPILE
La primera sentencia no compila porque está tratando de asignar un double a un valor int. A pesar de que el valor es un entero, al sumar “. 0”, está indicando al compilador que lo trate como un double. La segunda declaración no compila porque el valor 1921222 está fuera del rango de short. La tercera declaración no compila debido a la “f” añadida al final del número que indica al compilador que ha de tratar al número como un float. Finalmente, el último enunciado no compila porque Java interpreta el valor como un int y nota que es mayor de lo que permite int. El último caso necesitaría un postfix L para ser considerado un long.

4.4.2. Casting de valores primitivos

Se pueden arreglar los ejemplos de la sección anterior, haciendo “casting” a los resultados. El “casting” es necesario siempre que se pase de un dato numérico más grande a un tipo de datos numéricos más pequeños, o la conversión de un número float a un valor int.

int x = (int)1.0; 

short y = (short)1921222; // Stored as 20678 int

z = (int)9l;

long t = 192301398193810323L;
Algunos ejemplos:
short x = 10; 

short y = 3;

short z = x * y; // DOES NOT COMPILE
Basando todo en lo que se ha aprendido hasta ahora, ¿por qué las últimas líneas de esta declaración no compilarán? Recordar, los valores short se transforman automáticamente a int con cualquier operador aritmético, con el valor resultante de tipo int. Tratar de establecer una variable corta en una int resulta en un error del compilador, como piensa Java, está intentando convertir implícitamente de un tipo de datos más grande a uno más pequeño. Hay ocasiones en las que se puede querer anular el comportamiento predeterminado del compilador. Por ejemplo, en el ejemplo anterior, se sabe que el resultado de 10 * 3 es 30, que puede ser guardado en un short. Sin embargo, si se necesita que el resultado sea un short puede sustituirse este comportamiento haciendo “casting” del resultado de la multiplicación:
short x = 10; short y = 3;

short z = (short)(x * y);
Al realizar este “casting” de un tipo de datos mayor a un tipo de datos más pequeño, se está ordenando al compilador que ignore su comportamiento predeterminado. En otras palabras, se le está diciendo al compilador que se está tomando medidas adicionales para evitar el overflow o el underflow.

Overflow y Underflow

Las expresiones en el ejemplo anterior ahora compilan, aunque hay un coste. El segundo valor, 1.921.222, es demasiado grande para ser almacenado como un valor short, por lo que se produce un overflow y se convierte en 20.678. El overflow es cuando un número es tan grande que ya no se puede guardar dentro de un tipo de datos, por lo que el sistema se "envuelve" en el siguiente valor mínimo y

cuenta desde ahí. También hay un underflow analogo, cuando el número es demasiado bajo para guardarlo en el tipo de datos. Por ejemplo, la siguiente sentencia genera un número negativo:

_System.out.println(2147483647+1); // - 214748364848_
Dado que 2147483647 es el valor máximo de int, sumando cualquier valor estrictamente positivo a él se obtendrá lo siguiente al siguiente número negativo.

4.4.3. Operadores de asignación compuestos

Además del operador simple de asignación, =, existen también numerosos operadores de asignación compuestos. Sólo se requieren dos de los operadores compuestos enumerados en la Tabla 2.1, += y =. Los operadores complejos son en realidad una mejora de los operadores de asignación simple con una operación aritmética o lógica incorporada que se aplican de izquierda a derecha de la expresión y almacena el valor resultante en la variable de la parte izquierda de la pantalla. Por ejemplo, las dos expresiones siguientes después de la declaración de x y z son equivalentes:

int x = 2, z = 3; x = x * z; // Simple

assignment operator x *= z; // Compound

assignment operator
El lado izquierdo del operador compuesto sólo se puede aplicar a una variable que ya esté definida y no se puede utilizar para declarar una nueva variable. En el ejemplo anterior, si x no estaba ya definida, entonces la expresión x *=z no compilaría. Los operadores compuestos son útiles para algo más que la mera abreviatura, también se puede ahorrar el tener que hacer “casting” a un valor. Por ejemplo, si se considera el siguiente ejemplo en que la última línea no compilará debido a que el resultado es transformado a long y asignado a una variable int:
long x = 10; int y = 5;

y = y * x; // DOES NOT COMPILE
Si uno se basa en las dos últimas secciones, se debería poder detectar el problema en la última línea. Esta última línea podría ser fijada con un “casting” a int, pero hay una manera mejor, usando el operador de asignación compuesto:
long x = 10;

int y = 5; 

y*= x;
El operador compuesto hará un “casting” primero de x a long, aplicará la multiplicación de dos valores long, y luego devolverá el resultado a un int. A diferencia del ejemplo anterior, en el que el compilador lanzará una excepción, en este ejemplo vemos que el compilador hará un “casting” automáticamente del valor resultante al tipo de datos de la variable del lado izquierdo del operador compuesto. Una cosa importante que se debe saber acerca del operador de asignación es que el resultado de la asignación es una expresión en sí misma, igual al valor de la asignación. Por ejemplo, el siguiente fragmento de código es perfectamente válido, aunque un poco extraño:

long x = 5; long

y = (x=3);

System.out.println(x); // Outputs 3

System.out.println(y); // Also, outputs 3
La clave aquí es que (x=3) hace dos cosas.

  • En primer lugar, fija el valor de la variable x para que sea 3.
  • En segundo lugar, devuelve el valor de la asignación, que también es 3.

4.4.4. Operadores relacionales

Ahora se pasará a los operadores relacionales, que comparan dos expresiones y devuelven un valor booleano. Los primeros cuatro operadores relacionales (ver la tabla se aplican solo a tipos de datos primitivos numéricos. Si los dos operando numéricos no son del mismo tipo de datos, el parámetro más pequeño se transforma de la manera anteriormente discutida.

Operador relacional Descripción
< Estrictamente menos que
<= Menos que o igual a
> Estrictamente mayor que
>= Mayor que o igual a

A continuación, se verán unos ejemplos de estos operadores:

int x = 10, y = 20, z = 10;

System.out.println(x < y); // Outputs true

System.out.println(x <= y); // Outputs true

System.out.println(x >= z); // Outputs true

System.out.println(x > z); // Outputs false
Observar que el último ejemplo produce una salida false, porque, aunque x y z son el mismo valor, x no es estrictamente mayor que z. El quinto operador relacional (ver la tabla de abajo) se aplica a objetos y clases, o bien a interfaces.

Operador relacional Descripción
a instanceof b Verdadero si la referencia a la que apunta “a” es una instancia de una clase, subclase o clase que implementa una interfaz particular, como se nombra en b

Cabe destacar lo siguiente respecto al operador instanceof:

String str = null;

System.out.println(str instanceof String ); // false
Es decir: no basta con declarar la variable como de un tipo sino que debe ser realmente un objeto de ese tipo (como consecuencia, no es necesario comparar contra null).
if (x != null && x instanceof X) ...
Se puede simplificar a:
if (x instanceof X) ...

4.4.5. Operadores lógicos

Los operadores lógicos, (&), (|) y (^), se pueden aplicar a datos de tipo numéricos y booleanos. Cuando se aplican a tipos de datos booleanos, se los denomina operadores lógicos. Alternativamente, cuando se aplican a tipos de datos numéricos, se les llama operadores a nivel de bit, ya que realizan comparacionesbit a bit de los bits que componen el número. Se debe familiarizar con las tablas de las figuras, donde se supone que x e y son tipos de datos booleanos.

X & Y (AND)
Y=true Y=false
X = true True False
X = false False False
X | Y (Inclusive OR)
Y=true Y=false
X = true True True
X = false True False
X ^ Y (Exclusive OR)
Y=true Y=false
X = true False True
X = false True False

Aquí hay algunos consejos para ayudar a recordar esta tabla:

  • AND solo es verdadero si ambos operandos son verdaderos.
  • OR inclusivo solo es falso si ambos operandos son falsos.
  • Exclusive OR solo es verdadero si los operandos son diferentes. Finalmente, se presentan los operadores condicionales, && y ||, que a menudo se conocen como operadores de cortocircuito. Los operadores de cortocircuito son casi idénticos a los operadores lógicos, & y |, respectivamente, excepto que el lado derecho de la expresión nunca puede ser evaluado si el resultado final puede ser determinado por el lado izquierdo de la expresión. Por ejemplo, si se considera la siguiente declaración:
    boolean x = true || (y < 4);
    
    En referencia a las tablas de verdad, el valor x solo puede ser falso si ambos lados de la expresión son falsas. Como sabemos que el lado izquierdo es verdadero, no hay necesidad de evaluar el lado derecho, ya que nada hará que el valor de x sea diferente de verdadero. Para ilustrar este concepto prueba a ejecutar la línea de código anterior para varios valores de y. El ejemplo más común de dónde se usan los operadores de cortocircuito es la comprobación de objetos nulos antes de realizar una operación, como esta:
    if(x != null && x.getValue() < 5) {
    // Do something
    }
    
    En este ejemplo, si x fuera nulo, entonces el cortocircuito evita lanzar una excepción NullPointerException, ya que la evaluación de x.getValue () <5 nunca se alcanza. Alternativamente, si usamos un & lógico, entonces ambos lados siempre se evaluarán y cuando x fuese nulo esto arrojaría una excepción:
    if(x != null & x.getValue() < 5) { 
    // Throws an exception if x is null
    // Do something
    }
    
    ¿Cuál es el resultado del siguiente código?
    int x = 6;
    
    boolean y = (x >= 6) || (++x <= 7);
    
    System.out.println(x);
    
    Como x >= 6 es verdadero, el operador de incremento en el lado derecho de la expresión nunca se evalúa, por lo que la salida es 6.

4.4.6. Operadores de igualdad

La determinación de la igualdad en Java puede no ser trivial, ya que hay una diferencia semántica entre "dos objetos son lo mismo" y "dos objetos son equivalentes". Es aún más complicado por el hecho de que para tipos primitivos numéricos (entre los que se incluye también el tipo de dato char) y booleanos, no existe tal distinción. Comenzando por lo más sencillo: el operador de igualdad (==) y el operador de desigualdad (!=). Como los operadores relacionales, comparan dos operandos y devuelven un valor booleano si las expresiones o valores son iguales o no iguales, respectivamente. Los operadores de igualdad se utilizan en uno de tres casos:

  1. Comparando dos tipos primitivos numéricos. Si los valores numéricos son de diferente tipo de datos, los valores se transforman automáticamente como se describió anteriormente.
    Por ejemplo, 5 == 5.00 devuelve verdadero ya que el lado izquierdo se transforma a un double.
  2. Comparando dos valores booleanos.
  3. Comparando dos objetos, incluidos los valores nulos y String. Las comparaciones para la igualdad se limitan a estos tres casos, por lo que no se pueden mezclar tipos.
    Por ejemplo, cada uno de los siguientes devolvería en un error del compilador:
       boolean x = true == 3; // DOES NOT COMPILE boolean
    
       y = false != "Giraffe"; // DOES NOT COMPILE boolean
    
       z = 3 == "Kangaroo"; // DOES NOT COMPILE
    

Si se observa el siguiente fragmento:

boolean y = false; 

boolean x = (y = true);

System.out.println(x); // Outputs true
A primera vista, se podría pensar que la salida debería ser falsa, y si la expresión fuese (y == true), se tendría razón. En este ejemplo, sin embargo, la expresión está asignando el valor true a y, y como se vio en la sección de operadores de asignación, la asignación misma tiene el valor de la asignación. Por lo tanto, la salida sería verdadera.

Para la comparación de objetos, el operador de igualdad se aplica a las referencias a estos objetos, no a los objetos a los que apuntan. Dos referencias son iguales si y solo si apuntan a lo mismo objeto, o ambos apuntan a nulo. Si se ven algunos ejemplos se entenderá mejor:

File x = new File("myFile.txt");

File y = new File("myFile.txt");

File z = x;

System.out.println(x == y); // Outputs false

System.out.println(x == z); // Outputs true
Aunque todas las variables apuntan a la misma información de archivo, solo dos, x y z, son iguales en términos de ==.
En el Capítulo 3, "Core Java APIs", se continuará la discusión sobre la igualdad de objetos introduciendo lo que significa que dos objetos diferentes sean equivalentes. También se tratará la igualdad de los String y se mostrará cómo este puede ser un tema no trivial.

4.5. Comprender las sentencias de Java

Los operadores Java permiten crear muchas expresiones complejas, pero están limitadas en la manera en que pueden controlar el flujo del programa. Por ejemplo, imaginar que se quiere una sección de código que solo sea ejecutado bajo ciertas condiciones que no pueden ser evaluadas hasta que no se ejecute. O si se desea que un segmento particular de código se repita una vez por cada elemento en alguna lista. Como se dijo en el Capítulo 1, una declaración de Java es una unidad completa de ejecución en Java, terminando con un punto y una coma (;). En el capítulo, se mostrarán varias declaraciones de control de flujo en Java. Las declaraciones de control de flujo rompen el flujo de ejecución mediante la toma de decisiones, el bucle y la ramificación, permitiendo que la aplicación seleccione segmentos particulares del código para ejecutar.
Las declaraciones se pueden aplicar a expresiones simples, o a un bloque de código. Un bloque de código en Java es un grupo de cero o más declaraciones entre llaves, ({}), y se puede usar en cualquier lugar donde se permita usar una declaración simple.

4.5.1. if-then

De vez en cuando, solo se quiere ejecutar un bloque de código según ciertas condiciones. El if-then, como se muestra en el código siguiente, permite que la aplicación ejecute un bloque particular de código si y solo si una expresión booleana se evalúa como verdadera en tiempo de ejecución.

if(booleanExpression){
//Branch if true }
Por ejemplo, si se tiene una función que utiliza la hora del día para mostrar un mensaje al usuario:
if(hourOfDay < 11)
System.out.println(“Good Morning”);
Si la hora es menor que 11 entonces la función mostrará el mensaje. Si se quisiera además incrementar algún valor, morningGreetingCount, cada vez que el mensaje se muestra se podría repetir la declaración if-then, pero Java permite escribirlo a continuación del output convirtiendo el código en un bloque:
if(hourOfDay < 11){
System.out.println(“Good Morning”); 
morningGreetingCount++;
}
El bloque permite que se ejecuten múltiples instrucciones basadas en la evaluación del if-then. Hay que tener en cuenta que la primera instrucción no contiene un bloque dentro de la sección de impresión, pero podría tenerlo. Para mejorar la legibilidad del código, se considera una buena práctica colocar los bloques dentro de las sentencias if-then, así como muchas otras sentencias de control de flujo, aunque no es obligatorio.

a. Sangría y llaves Echar un vistazo a esta forma ligeramente modificada del ejemplo:
if(hourOfDay < 11)
System.out.println("Good Morning"); 
morningGreetingCount++;
Si se observan las sangrías, se puede pensar que la variable morningGreetingCount solo se va a incrementar cuando la hora sea menor que 11, pero no es lo que hace el código. Mostrará el mensaje solo si la hora es menor que 11 pero incrementará el valor de la variable siempre. Recuerda que en Java los espacios en blanco no son considerados parte de la ejecución.

4.5.2. if-then-else

Se va a complicar el ejemplo anterior, ¿qué pasaría si se quisiera mostrar otro mensaje cuando la hora sea mayor o igual a 11?

if(hourOfDay < 11) {
System.out.println("Good Morning");
}

if(hourOfDay >= 11) {
System.out.println("Good Afternoon");

}
Esto es un poco redundante, ya que estamos evaluando hourOfDay dos veces, y esto puede ser computacionalmente costoso. Java permite solucionar esto gracias a la sentencia if-then-else que se muestra en el siguiente código:
if(booleanExpression){

// Branch if true

} else {

// Branch if false
}
Veamos este ejemplo:
if(hourOfDay < 11) {

System.out.println("Good Morning");

} else {

System.out.println("Good Afternoon");
}
Ahora el código se está ramificando entre una de las dos opciones posibles, con la evaluación booleana solo una vez. El operador else utiliza una declaración o un bloque, de la misma manera que la sentencia if. De esta manera, se puede agregar declaraciones if-then adicionales a un bloque else para llegar a un ejemplo más refinado:
if(hourOfDay < 11) {

System.out.println("Good Morning");

    }else if(hourOfDay < 15) {

    System.out.println("Good Afternoon");

    }else{

System.out.println("Good Evening");
}
En este ejemplo, el proceso de Java continuará la ejecución hasta que encuentre un if-then que se evalúe como verdadero. Si ninguna de las dos primeras expresiones es verdadera, se ejecutará el código del bloque else del final. Una cosa a tener en cuenta al crear complejas declaraciones if-then-else es que el orden es importante. Por ejemplo, si se reordena el fragmento de código anterior de la siguiente manera:
if(hourOfDay < 15) {

System.out.println("Good Afternoon");

    } else if(hourOfDay < 11) {

        System.out.println("Good Morning"); // UNREACHABLE CODE

    } else {

System.out.println("Good Evening");
}
Para horas menores que 11, este código se comporta de forma diferente al anterior. Si un valor es menor que 11, será también menor que 15. Por lo tanto, si se puede alcanzar la segunda rama en el ejemplo, también se puede alcanzar la primera rama. Ya que la ejecución de cada rama es excluyente, solo una rama se puede ejecutar, si se ejecuta la primera rama no se podrá ejecutar la segunda. Por lo tanto, no hay forma de que la segunda rama se ejecute alguna vez, y el código se considera inalcanzable.

a. Verificando si la sentencia if se evalúa en una expresión booleana Echar un vistazo a las siguientes líneas de código:

int x = 1; 
if(x) { 
// DOES NOT COMPILE ...
}
Esta declaración puede ser válida en algunos otros lenguajes de programación y scripting, pero no en Java, donde 0 y 1 no se consideran valores booleanos. Además, hay que tener cuidado con los operadores de asignación que se utilizan como si fueran operadores iguales (==) en declaraciones if-then:
int x = 1; if(x = 5) { // DOES NOT COMPILE ...
}

4.5.3. Operador Ternario

Ahora que se ha visto las declaraciones if-then-else, se puede volver brevemente a la discusión de los operadores y presentar al último operador. El operador condicional,? :, también conocido como operador ternario, es el único operador que utiliza tres operandos y tiene la forma: booleanExpression? expression1 : expression2

El primer operando debe ser una expresión booleana, y el segundo y el tercero pueden ser cualquier expresión que devuelva un valor. La operación ternaria es realmente una forma condensada de un ifthen- else que devuelve un valor. Por ejemplo, los siguientes dos fragmentos de código son equivalentes:

int y = 10;
final int x;
if(y > 5) {
x = 2 * y; }
else { x =
3 * y; }
Comparar el código anterior con el siguiente código equivalente con el operador ternario:
int y = 10;
int x = (y > 5)? (2 * y) : (3 * y);
Tener en cuenta que a menudo es útil para la legibilidad agregar paréntesis alrededor de las expresiones en operaciones ternarias, aunque no es necesario. No es necesario que la segunda y tercera expresiones tengan los mismos tipos de datos, aunque puede entrar en juego cuando se combina con la asignación operador. Comparar las siguientes dos afirmaciones:
System.out.println((y > 5)? 21 : "Zebra");
int animal = (y < 91)? 9 : "Horse"; // DOES NOT COMPILE
Ambas expresiones evalúan valores booleanos similares y devuelven un int y un String, aunque solo se compilará la primera línea. El System.out.println () no se preocupa de que las sentencias sean de tipos completamente diferentes, ya que pueden convertir ambos a String. Por otro lado, el compilador sabe que "Horse" es de un tipo de datos incorrecto y no puede ser asignado a un int; por lo tanto, no permitirá que se compile el código.

a. Evaluación de las expresiones ternarias A partir de Java 7, solo una de las expresiones de la derecha del operador ternario será evaluado en tiempo de ejecución. De manera similar a los operadores de cortocircuito, si una de las dos expresiones de la derecha del operador ternario realiza un efecto secundario, entonces no se puede aplicar en tiempo de ejecución. Vamos a ilustrar este principio con el siguiente ejemplo: int y = 1; int z = 1; final int x = y<10? y++ : z++; System.out.println(y+","+z); // Outputs 2,1 Tener en cuenta que como la parte de la izquierda de la expresión es verdadera solo se incrementa y. Contrástar con el siguiente ejemplo:


int y = 1; int z = 1; final int x = y>=10? y++ : z++; System.out.println(y+","+z); // Outputs 1,2 Ahora que la expresión booleana de la izquierda se evalúa como falsa, solo z se incrementa. De esta manera, se ve cómo las expresiones en un operador ternario pueden no aplicarse si la expresión particular no se usa.
### 4.5.4. switch
Una sentencia switch, como se muestra en la Figura 2.4, es una estructura compleja de toma de decisiones en el que se evalúa un solo valor y el flujo se redirige a la primera rama correspondiente, conocida como “case”. Si no se encuentra dicha declaración de caso que coincida con el valor, una opción predeterminada será ejecutada. Si no hay tal opción predeterminada disponible, la totalidad de la sentencia switch será omitida.

a. Tipos de datos soportados Como se muestra en el siguiente código, una instrucción switch tiene una variable que se evalúa en tiempo de ejecución. Antes de Java 5.0, esta variable solo podía ser valores int o aquellos valores que podían ser transformados a int, de forma específica byte, short, char o int. Cuando se agregó “enum” en Java 5.0, se agregó el soporte para que los switch pudiesen admitir valores “enum”. En Java 7, las sentencias switch se actualizaron aún más para permitir los valores string. Finalmente, la sentencia switch es compatible con cualquiera de las clases primitivas de carácter numérico, como Byte, Short, Character o Integer.
switch(variableToTest) { case
constantExpression1: // Branch
for case1; break; case
constantExpression2: // Branch
for case2; break;
...

default:
}
Tipos de datos soportados por la sentencia switch:
- int y Integer
- byte y Byte
- short y Short
- char y Character
- String
- Valores enum
b. Valores constantes en tiempo de compilación Los valores de cada case deben ser del mismo tipo de dato que el valor introducido en el switch. Esto significa que solo se pueden utilizar literales, constantes enum o variables constantes finales del mismo tipo de datos. Ejemplo:

int dayOfWeek = 5; switch(dayOfWeek) { default:

System.out.println("Weekday"); break; case 0:
System.out.println("Sunday");
break; case 6:
System.out.println("Saturday");
break;
}
Con el valor de dayOfWeek de 5 el código devolverá “Weekday”.
Lo primero que se puede notar es que hay una sentencia break al final de cada case y la sección
predeterminada(default). Se discutirá las sentencias break en detalle cuando se vean los bucles, pero
por ahora, todo lo que se necesita saber es que finaliza la sentencia switch y el control de flujo vuelve a
la sentencia adjunta. Como se verá pronto, si se omite la declaración del break, el flujo continuará hasta
el siguiente caso en curso o bloque predeterminado.
Otra cosa que se puede notar es que el bloque predeterminado no está al final del switch. No es un
requisito que el caso o las declaraciones predeterminadas estén en un orden en particular, a menos que
tenga vías que lleguen a múltiples secciones del bloque switch en una sola ejecución.
Para ilustrar lo aprendido se considerará la siguiente variación:
int dayOfWeek = 5;
switch(dayOfWeek) {
case 0:
System.out.println("Sunday");
default:
System.out.println("Weekday"); case
6 :
System.out.println("Saturday");
break;
}
Este código se parece mucho al ejemplo anterior, excepto que dos de las declaraciones break han sido eliminadas y el orden ha cambiado. Esto significa que por el valor dado de dayOfWeek, 5, el código saltará al bloque predeterminado y luego ejecutará todos los case en orden hasta que encuentre una sentencia break o finalice la estructura. El orden de los case y del bloque predeterminado es importante ahora ya que dejando el bloque predeterminado al final del switch, este, solo devolverá una palabra. Si el valor de dayOfWeek fuese 6 el switch devolvería “Saturday”. Aunque el bloque predeterminado estaba antes del bloque case, solo se ejecutó el bloque case. Si se recuerda la definición del bloque predeterminado, solo se ramificará si no ha coincidido el valor del caso con el valor del switch, independientemente de su posición dentro del switch. Por último, si el valor de dayOfWeek fuese 0, la salida mostraría:
Sunday
Weekday


Saturday
Tener en cuenta que, en este último ejemplo, se ejecuta el bloque predeterminado porque no hay una instrucción break al final de los bloques de case anteriores. Mientras el código no se ramifique hacia la sentencia predeterminada si hay un valor de case que coincida dentro de la declaración del switch, se ejecutará la instrucción predeterminada si se encuentra después de una declaración de case para la que no hay declaración de break. Conclusión, se acepta que el tipo de datos para las declaraciones de case, debe coincidir con el tipo de datos de la variable del switch. Como ya se discutió, el valor de la declaración de case también debe ser una constante literal, enum o una variable final. Por ejemplo, dada la siguiente instrucción switch, observar qué afirmaciones de case compilarán y cuales no:
private int getSortOrder(String firstName, final String lastName) {
String middleName = "Patricia"; final String suffix = "JR"; int
id = 0; switch(firstName){ case "Test": return 52;
case middleName: // DOES NOT COMPILE id = 5;
break; case suffix: id = 0; break; case lastName: // DOES NOT COMPILE id = 8; break; case 5: // DOES NOT COMPILE id = 7; break; case 'J': // DOES NOT COMPILE id = 10; break; case java.time.DayOfWeek.SUNDAY: // DOES NOT COMPILE id = 15; break; } return id; } La primera declaración de case compila sin problemas usando un String, y es un buen ejemplo de cómo una declaración de return, se puede usar para salir del switch. La segunda declaración de case no se compila porque middleName no es una variable final, a pesar de tener un valor conocido en esta línea de ejecución en particular. La tercera declaración compila sin problema porque suffix es una variable constante final. La cuarta declaración, aunque LastName sea final, no es una constante ya que se ha pasado a la función, por lo tanto, esta línea tampoco compila. Finalmente, los tres últimos case no compilan porque ninguno de ellos coincide con el tipo String de la variable del switch, el último es de tipo enum value.
### 4.5.5. while
Una estructura de control de repetición, también conocida como bucle, ejecuta el mismo código varias veces seguidas. Mediante el uso de variables no constantes, cada repetición de la sentencia puede ser

diferente. Por ejemplo, una declaración que se itera sobre una lista de nombres únicos y las salidas devolverán un nuevo nombre en cada ejecución del bucle. La estructura de control de repetición más simple en Java es la instrucción while, descrita en el siguiente código. Como todas las estructuras de control de repetición, tiene una condición de terminación, implementada como una expresión booleana, que continuará mientras la expresión se evalúe a true.
while(booleanExpression){
//Body
}
Como se muestra en el código, un bucle while es similar a las sentencias if-then ya que está compuesta
por una expresión booleana e instrucciones o bloque de instrucciones. Durante la ejecución, la expresión
booleana es evaluada antes de cada iteración del bucle y termina si la evaluación devuelve un false. Es
importante darse cuenta que el bucle while puede terminar después de su primera evaluación de la
expresión booleana. De esta forma el bloque de instrucciones puede no ejecutarse nunca.
Si se vuelve al ejemplo de ratón del capítulo 3 y se muestra un bucle que puede ser utilizado para
modelar un ratón comiendo una comida:
int roomInBelly = 5; public void eatCheese(int bitesOfCheese) { while(bitesOfCheese > 0 && roomInBelly > 0) { bitesOfCheese--; roomInBelly--; } System.out.println(bitesOfCheese+" pieces of cheese left"); } Este método coge una cantidad de comida, en este caso de queso, y continua hasta que el ratón no tenga más espacio en su estómago o no quede comida para comer. Con cada iteración del bucle, el ratón come un trozo de comida y pierde una parte del espacio en su estómago. Utilizando una sentencia booleana compuesta, nos aseguramos de que el bucle while pueda terminar por cualquiera de las condiciones.

a. Bucles infinitos Considerar el siguiente código:
int x = 2; int
y = 5; while(x
< 10) y++;
Se puede observar un evidente problema con esta declaración: nunca acabará. La expresión booleana que se evalúa antes de cada iteración del bucle nunca se modifica por lo que la expresión (x < 10) siempre será evaluada a true. El resultado es que el bucle nunca acabará creando lo que se conoce como bucle infinito. Los bucles infinitos son algo que se debe tener en cuenta al crear bucles. Se tiene que estar seguro de que el bucle termina bajo alguna condición. Primero comprobar que la variable del bucle se modifica. Luego, asegurarse que la condición de terminación se alcanza bajo cualquier circunstancia. Como se verá en “Entendiendo los flujos de control avanzados” de un bucle se puede salir también con otras condiciones como con la sentencia break.
### 4.5.6. do-while
Java también permite crear bucles do-while, que como al bucle while, es una estructura de control de repetición con una condición de terminación e instrucciones o bloques de instrucciones, como se muestra en el siguiente código. Al contrario que el bucle while, el bucle do-while garantiza que la instrucción o el bloque de instrucciones se ejecutará mínimo una vez.
do{
//Body
}while(booleanExpression);
La principal diferencia entre la estructura del do-while y del while es que ordena intencionadamente las
instrucciones o los bloques de instrucciones antes de la expresión condicional, para recalcar que la
instrucción será ejecutada antes de evaluar la expresión. Por ejemplo, mirar la salida del siguiente
código:

int x = 0;
do {
x++;
}while(false);
System.out.println(x); // Outputs 1
Java ejecutará primero el bloque de instrucciones, y luego comprobará la condición del bucle. A pesar de
que el bucle termina inmediatamente, el bloque de instrucciones se ejecuta una vez y el programa
devuelve 1.
a. Cuando usar bucles while o do-while En la práctica, puede ser difícil determinar cuándo se debe utilizar un bucle while y un bucle do-while. No importa cual se utilice ya que cualquier bucle while se puede convertir en un bucle do-while y viceversa. Comparar estos dos bucles:
while(x > 10)
{ x--; }
if(x > 10) {
do { x--;
}while(x > 10);
}
Aunque uno de los bucles es más fácil de leer, son funcionalmente iguales. Java recomienda utilizar los
bucles while cuando no sea necesario ejecutar sus instrucciones, y utilizar los bucles do-while cuando
sea imprescindible que se ejecute como mínimo una vez, pero, en la práctica, elegir entre uno u otro es
algo personal.
Por ejemplo, pese a que la primera declaración es más corta, la segunda tiene una ventaja que permite
hacer uso de la sentencia if-then y realizar otras operaciones en la rama del else, como se muestra en
el ejemplo:
if(x > 10) { do { x-- ; }while(x > 10); }

else { x++; }
### 4.5.7. La sentencia for
Ahora se extenderá el conocimiento con otra estructura de control repetición llamada “for”. Existen dos tipos de sentencia for, la primera se refiere al bucle simple for, la segunda se llama for-each que es una mejora de la sentencia for.

a. La sentencia for simple Un bucle for simple tiene la misma condición booleana o bloque de instrucciones que los otros bucles que hemos visto, e incluye dos nuevas secciones: la inicialización y la actualización. El siguiente código muestra como estos componentes están organizados:
for(initialization; booleanExpression; updateStatement) {
// Body
}

El código puede parecer algo confusa y arbitraria al principio, la organización de los componentes y el
flujo permite crear poderosas sentencias en poco espacio al contrario de otros bucles que necesitarían
múltiples líneas. Fijarse que cada sección está separada por punto y coma (;) y la inicialización y la
actualización pueden contener varias sentencias separadas por comas.
Las variables creadas en el bloque de inicialización tienen un ámbito limitado y solo serán accesibles
dentro del bucle for. Alternativamente, las variables declaradas antes del for y modificadas en el bloque
de inicialización pueden ser utilizadas fuera del bucle for ya que su ámbito es anterior a la creación del
bucle for.
A continuación, este ejemplo imprime en pantalla los números del 0 al 9:
for(int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
La variable local i se inicializa a 0. Esta variable tiene un ámbito dentro del bucle y no es accesible desde fuera del bucle una vez el bucle haya terminado. Como en los bucles while, la expresión booleana es evaluada en cada iteración del bucle antes de que se ejecuten las instrucciones del este. Si devuelve un true, el bucle se ejecuta e imprime el 0 seguido de un espacio en blanco. Luego el bucle ejecuta el bloque de actualización, que en este caso incrementa el valor de i a 1. Entonces el bucle evalúa otra vez la expresión booleana y el proceso se repite varias veces, imprimiendo lo siguiente:
0 1 2 3 4 5 6 7 8 9
En la décima iteración del bucle, el valor de i alcanza el 9 y se incrementa a 10. En la undécima iteración del bucle la expresión booleana será evaluada a falso ya que 10 no es menor que 10, el bucle terminará sin ejecutar el bloque de instrucciones.

Ejemplos con los que familiarizarse:
- Creación de un bucle infinito
  for( ; ; ) {
  System.out.println("Hello World");
  }
Puede parecer que este bucle for dará problemas de compilación, pero compilará y funcionará sin problemas. Es un bucle infinito que imprimirá el mismo mensaje repetidas veces. Este ejemplo refuerza la idea de que los componentes del bucle for son opcionales, y recuerda que el punto y coma que separa las secciones son necesarios, for() no compilará.
- Adición de varios términos a la sentencia for

int x = 0;
for(long y = 0, z = 4; x < 5 && y < 10; x++, y++) {
System.out.print(y + " ");
}
System.out.print(x);
Este código demuestra tres variaciones del bucle for que puede que aún no se hayan visto. Primero, se puede declarar una variable, como x en el ejemplo, antes de que el bucle empiece y usarla después de que termine. Segundo, el bloque de inicialización, la expresión booleana, y el bloque de actualización, pueden incluir variables extra que pueden no referenciarse entre ellas. Por ejemplo, z está definida en el bloque de inicialización y nunca se usa. Finalmente, el bloque de actualización puede modificar múltiples variables. El código imprimirá:
0 1 2 3 4

- Re-declarando una variable en el bloque de inicialización
  int x = 0;
  for(long y = 0, x = 4; x < 5 && y < 10; x++, y++) { // DOES NOT COMPILE
  System.out.print(x + " ");
  }
  Este ejemplo parece similar al anterior, pero no compila debido al bloque de inicialización. La diferencia
  es que x se repite en el bloque de inicialización después de haber sido declarado ya antes del loop, dando
  lugar a que el compilador se detenga debido a una declaración de variable duplicada. Se puede fijar este
  bucle cambiando la declaración de x y y como sigue:
  int x = 0; long
  y = 10;
  for(y = 0, x = 4; x < 5 && y < 10; x++, y++) {
  System.out.print(x + " ");
  }
  Tener en cuenta que esta variación si compilará porque el bloque de inicialización simplemente asigna
  un valor a x y no lo declara.
- Utilizando tipos de datos incompatibles en el bloque de inicialización
  for(long y = 0, int x = 4; x < 5 && y<10; x++, y++) { // DOES NOT COMPILE
  System.out.print(x + " ");
  }
Este ejemplo también se parece mucho al segundo ejemplo, pero como en el tercer ejemplo no compilará, aunque esta vez por una razón diferente. Las variables del bloque de inicialización deben ser del mismo tipo. En el primer ejemplo, y y z eran ambos long, así que el código compilará sin problema, pero en este ejemplo tienen tipos diferentes, por lo que el código no se compilará.
- Usando variables del bucle fuera de este
  for(long y = 0, x = 4; x < 5 && y < 10; x++, y++) {
  System.out.print(y + " ");
  }
  System.out.print(x); // DOES NOT COMPILE
La variación final del segundo ejemplo no compilará por una razón diferente a la de los ejemplos anteriores. Fijarse que x se define en el bloque de inicialización del bucle, y luego se usa después de que el bucle termine. Puesto que x sólo tiene ámbito para el bucle, usarlo fuera del bucle lanzará un error de compilación.
### 4.5.8. La sentencia for-each
A partir de Java 5.0, los desarrolladores han tenido una mejora para el bucle for a su disposición, uno específicamente diseñado para iterar sobre arrays y colección de objetos. Esto mejora el bucle for, que para mayor claridad se hará referencia a él como un bucle for-each, como se muestra en el siguiente código:
for(datatype instance : collection){
//Body
}
La declaración del bucle for-each está compuesta por una sección de inicialización y un objeto para utilizarlo repetidas veces. El lado derecho de la sentencia for-each debe ser un array o un objeto que implemente la clase Java.lang.Iterable, que incluye la mayor parte de los frameworks de Java Collections. El lado izquierdo del bucle for-each debe incluir una declaración para una instancia de una variable, cuyo tipo coincida con el tipo de un miembro del array o de la colección en el lado derecho de la sentencia. Ejemplos:
final String[] names = new String[3]; names[0] = "Lisa";
names[1] = "Kevin"; names[2] = "Roger"; for(String name : names)
{ System.out.print(name + ", ");
}
Este código compilará e imprimirá: Lisa, Kevin, Roger,
java.util.List<String> values = new java.util.ArrayList<String>();
values.add("Lisa"); values.add("Kevin"); values.add("Roger"); for(String value
: values) { System.out.print(value + ", ");
}
Este código compilará e imprimirá: Lisa, Kevin, Roger,
String names = "Lisa";
for(String name : names) { // DOES NOT COMPILE
System.out.print(name + " ");
}

En este ejemplo, el String names no es un array y no implementa la clase java.lang.iterable, así que el
compilador lanzará una excepción ya que no sabe como iterar un String.
String[] names = new String[3]; for(int name : names) { // DOES NOT COMPILE
System.out.print(name + " ");
}
Este código no compilará porque en el lado izquierdo del bucle no se define una instancia de String.
Nótese que, en este último ejemplo, el array se inicializa con tres valores de puntero cero. En sí mismo,
eso no causará un fallo de compilación, sólo imprimirá tres veces cero.
a. Comparación del bucle for y el bucle for-each Dado que for y for-each usan la misma palabra clave, es posible preguntarse cómo están relacionados. Tomar un momento para explorar cómo el compilador convierte cada for-each en bucles for. Cuando se introdujo el bucle for-each en Java 5, se agregó como una mejora de compilación. Esto significa que Java realmente convierte el bucle for-each en un bucle for estándar durante compilación. Por ejemplo, asumiendo que names es un array de String[] como se vio en la primera parte. Ejemplo, los dos bucles siguientes son equivalentes:
for(String name : names) { System.out.print(name + ", ");
}
for(int i=0; i < names.length; i++) {
String name = names[i];
System.out.print(name + ", ");
}
Para los objetos que heredan de Java.lang.iterable, hay una conversión diferente, pero similar. Por ejemplo, asumiendo que los valores son una instancia de List, como vimos en el segundo caso ejemplo, los dos bucles siguientes son equivalentes:
for(int value : values) { System.out.print(value + ", ");
}
for(java.util.Iterator<Integer> i = values.iterator(); i.hasNext(); ) { int value =
i.next();
System.out.print(value + ", ");
}
Observar que, en la segunda versión, no hay declaración de actualización ya que no se requiere cuando se usa la clase java.util.Iterator. Es posible que se haya notado que, en los ejemplos anteriores, había una coma extra impresa al final de la lista:
Lisa, Kevin, Roger,
Mientras que la declaración for-each es conveniente para trabajar con listas en muchos casos, oculta el acceso a la variable iterator del bucle. Si se quisiera imprimir sólo la coma entre nombres, se podría convertir el ejemplo en un bucle for estándar, como en el ejemplo siguiente:

java.util.List names = new java.util.ArrayList(); names.add("Lisa"); names.add("Kevin"); names.add("Roger"); for(int i=0; i0) { System.out.print(", "); } System.out.print(name); } Este código de muestra produciría lo siguiente:
Lisa, Kevin, Roger
También es común utilizar un bucle for sobre un bucle for-each si se comparan múltiples elementos en un bucle dentro de una sola iteración, como en el ejemplo siguiente. Observar que se salta la ejecución del primer bucle, ya que el valor[-1] no está definido y arrojaría un IndexOutOfBoundsException error.

int[] values = new int[3]; values[0] = 10; values[1] = new Integer(5); values[2] = 15; for(int i=1; i<values.length; i++) { System.out.print(values[i]-values[i- 1]); } Este código devolverá lo siguiente:
- 5, 10,
A pesar de estos ejemplos, los bucles mejorados for-each son bastante útiles en Java en una gran variedad de circunstancias. Como desarrollador, sin embargo, siempre se puede volver a un for estándar si se necesita un control más fino.
## 4.6. Comprendiendo el control de flujo avanzado
Hasta ahora, se ha tratado con bucles simples que sólo terminaban cuando su expresión booleana es evaluada como falsa. Ahora se mostrará otras formas en la que los bucles podrían terminar, o ramificar, y se verá que el camino tomado durante el tiempo de ejecución puede no ser tan sencillo como en los ejemplos anteriores.
### 4.6.1. Bucles anidados
En primer lugar, los bucles pueden contener otros bucles. Por ejemplo, considerar el siguiente código que itera sobre una matriz bidimensional, una matriz que contiene otras matrices como sus miembros. Se cubrirá los arreglos multidimensionales en detalle en el Capítulo 3, pero por ahora se supondrá que la siguiente es la forma de declarar un arreglo bidimensional.
int[][] myComplexArray = {{5,2,1,3},{3,9,8,9},{5,7,12,7}}; for(int[] mySimpleArray :
myComplexArray) { for(int i=0; i<mySimpleArray.length; i++) {
System.out.print(mySimpleArray[i]+"\t");
}
System.out.println();
}
Nótese que en este ejemplo se mezcla intencionalmente un bucle for y uno for-each. Los bucles externos se ejecutarán un total de tres veces. Cada vez que se ejecuta el bucle exterior, el bucle interior se ejecutará cuatro veces. Cuando se ejecuta este código, se ve la siguiente salida:
5 2 1 3
3 9 8 9
5 7 12 7
Los bucles anidados pueden incluir bucles while y do-while, como se muestra en este ejemplo. Ver si se puede determinar lo que este código saldrá.
int x = 20;
while(x>0) {
do { x -=
2 }while
(x>5); x--;
System.out.print(x+"\t");
}
La primera vez que este bucle se ejecuta, el bucle interno se repite hasta que el valor de x es 4. El valor
será entonces decrementado a 3 y será la salida, al final de la primera iteración del bucle exterior. En la
segunda iteración del bucle exterior, el do-while interno se ejecutará una vez, aunque x no sea mayor
que 5. Recordar que las sentencias do-while siempre se ejecutan al menos una vez. Esto reducirá el valor
a 1, que será decrementado aún más por el operador de decremento en el bucle exterior a 0. Una vez
que el valor alcance 0, el bucle externo se termina. El resultado es que el código emitirá lo siguiente:
3 0

### 4.6.2. Añadiendo etiquetas opcionales
Una cosa que se omite cuando se presentan las sentencias if-then, las sentencias switch y bucles es que todos ellos pueden tener etiquetas opcionales. Una etiqueta es un puntero opcional a la cabeza de una instrucción que permite a la aplicación saltar a ella o acabar. Es una sola palabra que procede de dos puntos (:). Por ejemplo, se puede añadir etiquetas opcionales a uno de los ejemplos anteriores:
int[][] myComplexArray = {{5,2,1,3},{3,9,8,9},{5,7,12,7}}; OUTER_LOOP: for(int[]
mySimpleArray : myComplexArray) {
INNER_LOOP: for(int i=0; i<mySimpleArray.length; i++) {


System.out.print(mySimpleArray[i]+"\t");
}
System.out.println();
}
Cuando se trata de un solo bucle, no añaden ningún valor, pero como se verá en la siguiente sección, son extremadamente útiles en entornos anidados. Las etiquetas opcionales a menudo sólo se utilizan en estructuras de bucle. Dicho esto, rara vez se considera una buena práctica de codificación hacerlo. Las etiquetas siguen las mismas reglas para los identificadores. En cuanto a la legibilidad, se expresan comúnmente en mayúsculas, con barra baja entre palabras, para distinguirlas de las variables regulares.
### 4.6.3. La declaración break
Como se vio al trabajar con las sentencia switch, una declaración break transfiere el flujo de control a la declaración adjunta. Lo mismo es válido para las sentencias break que aparecen dentro de bucles while, do-while, y bucles for, ya que terminaran el bucle temprano, como se muestra en el siguiente código:

optionalLabel: while(booleanExpression) { //

Body // Somewhere in loop break optionalLabel; } Observar en el código que la sentencia break puede tomar un parámetro opcional de etiqueta. Sin un parámetro de etiqueta, la sentencia break terminará el bucle interno más cercano que esté ejecutando actualmente. El parámetro de etiqueta opcional permite salir de un bucle exterior de nivel superior. En el siguiente ejemplo, se busca la primera posición del índice de matriz (x, y) de un número dentro de una matriz bidimensional no clasificada:
public class SearchSample { public static
void main(String[] args) { int[][] list =
{{1,13,5},{1,2,5},{2,7,2}};
int searchValue = 2; int positionX = -1; int positionY = -1; PARENT_LOOP: for(int i=0; i<list.length; i++) { for(int j=0; j<list[i].length; j++) { if(list[i][j]==searchValue){ positionX = i; positionY = j; break PARENT_LOOP; } } } if(positionX==-1 || positionY==-1) { System.out.println("Value "+searchValue+" not found"); } else { System.out.println("Value "+searchValue+" found at: " + "("+positionX+","+positionY+ ")"); } } } Cuando se ejecute, este código saldrá:
Value 2 found at: (1,1)
En particular, échese un vistazo a la sentencia break PARENT_LOOP. Esta declaración romperá de toda la estructura del bucle tan pronto como se encuentre el primer valor que se ajuste. Ahora, imaginar lo que pasaría si se reemplazara el cuerpo del bucle interno por el siguiente:
if(list[i][j]==searchValue) { positionX = i; positionY =
j; break;
}

¿Cómo cambiaría esto la salida? En lugar de salir cuando se encuentra el primer valor que coincida, el
programa sólo saldrá del bucle interno cuando se cumpla la condición. En otras palabras, la estructura
ahora encontrará el primer valor que se ajuste del último bucle interno que contenga el valor, resultando
en la siguiente salida:
Value 2 found at: (2,0)
Finalmente, ¿qué tal si se elimina el break por completo?
if(list[i][j]==searchValue) {
positionX = i;
positionY = j;
}
En este caso, el código buscará el último valor de toda la estructura que tenga el valor equivalente. La salida se verá así:
Value 2 found at: (2,2)
Se puede ver en este ejemplo que el uso de una etiqueta en una sentencia break en un bucle anidado, o el no uso de la sentencia break en absoluto, puede hacer que el bucle se comporte de forma muy diferente.
### 4.6.4. La sentencia continue
Se completará ahora la discusión sobre el control avanzado del bucle con la sentencia continue, una declaración que causa que el flujo de datos finalice la ejecución del bucle actual, como se muestra en el siguiente código:
Se puede notar que la sintaxis de la declaración continue refleja la declaración break. De hecho, las
sentencias son similares en cuanto a su uso, pero con resultados diferentes. Mientras que la sentencia
break transfiere el control a la sentencia adjunta, la sentencia continue transfiere el control a la expresión
booleana que determina si el bucle debe continuar. En otras palabras, termina la iteración actual del
bucle. También, al igual que la declaración break, la declaración continue se aplica al bucle interno más
cercano en ejecución usando instrucciones de etiqueta opcionales para anular este comportamiento.
Ejemplo:
public class SwitchSample {
public static void main(String[] args) {
FIRST_CHAR_LOOP: for (int a = 1; a <= 4; a++) { for (char x = 'a'; x <= 'c'; x++)
{ if (a == 2 || x == 'b') continue FIRST_CHAR_LOOP;
System.out.print(" " + a + x);
}
}
}
}
Con la estructura definida, el bucle devolverá el control al bucle padre cada vez que el primer valor sea 2 o el segundo valor sea b. Esto resulta en una ejecución del bucle interno para cada una de las tres llamadas del bucle exterior. La salida se ve así:
1a 3a 4a
Ahora, imaginar que se elimina la etiqueta FIRST_CHAR_LOOP en la declaración continue para que el control sea devuelto al bucle interno en vez del externo. Mirar cómo cambiará la salida:
1a 1c 3a 3c 4a 4c
optionalLabel: while(booleanExpression) { // Body //

Somewhere in loop continue optionalLabel; }

Por último, si se elimina la sentencia continue y la sentencia if-then asociada a ésta, se llega a una estructura que produce todos los valores, tales como:
1a 1b 1c 2a 2b 2c 3a 3b 3c 4a 4b 4c
La siguiente tabla ayudará a recordar cuando se permiten etiquetas, break, y sentencias continue en Java. Aunque para fines ilustrativos los ejemplos han incluido el uso de estas sentencias en bucles anidados, también se pueden usar dentro de bucles simples.
(^) Permite etiquetas
opcionales
Permite
sentencias break
Permite
sentencias
continue
if Si No No
while Si Si Si
do while Si Si Si
for Si Si Si
switch Si Si No

### 4.6.1. Resumen
En este capítulo, se ha visto que: Entender los operadores de Java
- Cómo utilizar todos los operadores Java requeridos que se describen.
- Saber cómo influye la precedencia del operador en la forma en que se interpreta una expresión
  determinada.
Usando operadores binarios
- Los operadores de asignación son operadores binarios que modifican la variable con el valor del lado
  derecho de la ecuación.
- El “casting” es necesario siempre que se pase de un dato numérico más grande a un tipo de datos
  numéricos más pequeños, o la conversión de un número float a un valor int.
- Los operadores complejos son operadores de asignación con una operación aritmética o lógica
  incorporada que se aplican de izquierda a derecha de la expresión y almacena el valor resultante en la
  variable de la parte izquierda de la pantalla.
- Los operadores relacionales son aquellos que comparan dos expresiones y devuelven un valor booleano.
    - Los operadores lógicos, (&), (|) y (^), se pueden aplicar a datos de tipo numéricos y booleanos.
- Los operadores de igualdad comparan dos operandos y devuelven un valor booleano si las expresiones
  o valores son iguales, o no iguales, respectivamente.
Comprender las sentencias de Java
- Las estructuras de control: las estructuras de control de la toma de decisiones, incluidas las de tipo
  ifthen, if-then-else y switch, así como las estructuras de control de la repetición, incluidas for, for-each,
  while y do-while.


- La mayoría de estas estructuras requieren la evaluación de una expresión booleana en particular, ya
  sea para las decisiones de ramificación o una vez por repetición.
- La sentencia switch es la única que soporta una variedad de tipos de datos, incluyendo variables String
  a partir de Java 7.
- Con un for-each no es necesario escribir explícitamente una expresión booleana, ya que el compilador
  las construye implícitamente. Para mayor claridad, se refiere a un bucle for mejorado como un bucle
  foreach, pero sintácticamente están escritos como una declaración for.
Comprendiendo el control de flujo avanzado ``` - Los bucles pueden contener otros bucles, incluidos bucles while y do-while. - Una etiqueta es un puntero opcional a la cabeza de una instrucción que permite a la aplicación saltar a ella o acabar. - Cómo se puede mejorar el flujo a través de los bucles anidados, de sentencias break y de sentencias continue.