Control de flujo y arrays

El switch de Java ha cambiado tanto en los últimos años que el que aparece en los tutoriales viejos y el que se escribe hoy casi no se parecen. Esta lección va de eso, de los bucles, y de los arrays: por qué no crecen y por qué == no sirve para compararlos.

Lo de siempre, en dos líneas

if, else, while y for se escriben como en C, Java fue quien los copió primero. Lo que merece la pena mirar es el for de recorrido y el operador ternario:

for (String fr : frutas) IO.println("  for-each: " + fr);

int edad = 17;
IO.println((edad >= 18 ? "mayor" : "menor") + " de edad");
salidafor-each: pera
  for-each: uva
  for-each: kiwi
menor de edad

Y un detalle que evita muchos errores: && y || cortocircuitan. Si el primer operando ya decide el resultado, el segundo no llega a evaluarse:

String texto = null;
if (texto != null && texto.length() > 0) { ... }   // no revienta

Por eso este orden es idiomático y el contrario explota.

El switch de hoy no se parece al de ayer

El switch clásico es de las cosas más feas del lenguaje: hay que acordarse del break en cada rama o la ejecución se cae a la siguiente. Compáralos:

// el de toda la vida
String viejo;
switch (dia) {
    case 6:
    case 7:
        viejo = "fin de semana";
        break;
    default:
        viejo = "laborable";
}

// el moderno: es una expresión, devuelve un valor
String nuevo = switch (dia) {
    case 6, 7 -> "fin de semana";
    default   -> "laborable";
};
salidadía 5: viejo=laborable  nuevo=laborable
  día 6: viejo=fin de semana  nuevo=fin de semana
  día 7: viejo=fin de semana  nuevo=fin de semana

La versión con flecha no se cae a la rama siguiente, agrupa casos con comas y devuelve un valor, así que se puede asignar directamente. Si una rama necesita varias líneas, se abre una llave y se devuelve con yield:

int dias = switch (mes) {
    case 1, 3, 5, 7, 8, 10, 12 -> 31;
    case 4, 6, 9, 11 -> 30;
    case 2 -> {
        int anio = 2026;
        boolean bisiesto = (anio % 4 == 0 && anio % 100 != 0) || anio % 400 == 0;
        yield bisiesto ? 29 : 28;
    }
    default -> throw new IllegalArgumentException("mes inválido: " + mes);
};
salidafebrero de 2026 tiene 28 días
Cuando un switch es una expresión, el compilador exige que cubras todos los casos posibles. Si trabajas con un enum y añades un valor nuevo, todos los switch que lo usen dejarán de compilar hasta que los actualices. Es de las mejores redes de seguridad del lenguaje, y se pierde en cuanto escribes un default que no hace nada.

Y ahora también mira el tipo

String q = switch (o) {
    case null                  -> "nada";
    case Integer i when i > 10 -> "un entero grande: " + i;
    case Integer i             -> "un entero: " + i;
    case String s              -> "un texto de " + s.length() + " letras";
    case List<?> l             -> "una lista de " + l.size();
    default                    -> "otra cosa: " + o;
};
salidaun entero grande: 42
un texto de 4 letras
otra cosa: 3.5
una lista de 2
nada

Fíjate en lo que hace cada rama: comprueba el tipo y declara la variable ya convertida, sin cast. El when añade una condición extra, y case null permite tratar el nulo como un caso más en vez de reventar antes de empezar. Esto llegó en Java 21 y cambia bastante cómo se escribe el código que maneja varios tipos.

Arrays

int[] a = new int[4];           // se rellena con ceros
String[] b = new String[3];     // se rellena con null
double[] c = {1.5, 2.5, 3.5};
salidaint[4]    = [0, 0, 0, 0]
String[3] = [null, null, null]
literal   = [1.5, 2.5, 3.5]   longitud 3

Lo primero que hay que interiorizar: un array no cambia de tamaño. Lo que se declara es lo que hay. Si necesitas que crezca, lo que quieres es una List, y eso es la lección 7.

Lo segundo: se pregunta con length, sin paréntesis. En las cadenas es length() y en las listas size(). No hay lógica detrás, hay que recordarlo.

frutas[3] con tres frutasjava.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3

La clase Arrays, que resuelve casi todo

int[] n = {5, 3, 9, 1};
Arrays.sort(n);
IO.println("  ordenado   " + Arrays.toString(n));
IO.println("  binarySearch(9) -> " + Arrays.binarySearch(n, 9));
IO.println("  copyOf(2)  " + Arrays.toString(Arrays.copyOf(n, 2)));
IO.println("  equals     " + Arrays.equals(new int[]{1,2}, new int[]{1,2}));
IO.println("  con == ?   " + (new int[]{1,2} == new int[]{1,2}));
salidaordenado   [1, 3, 5, 9]
  binarySearch(9) -> 3
  copyOf(2)  [1, 3]
  equals     true
  con == ?   false
Esas dos últimas líneas son el examen. Dos arrays con el mismo contenido son equals pero no son ==, porque == compara referencias: pregunta «¿son el mismo objeto?», no «¿valen lo mismo?». Lo mismo pasa con String, y es el error número uno de quien empieza. Para comparar contenido: equals en objetos, Arrays.equals en arrays.

Y para imprimir: Arrays.toString. Si imprimes un array a secas sale algo como [I@1b6d3586, que es el tipo y la dirección. Para arrays de arrays, Arrays.deepToString.

Dos dimensiones

int[][] m = {{1,2,3},{4,5,6}};
for (int[] fila : m) IO.println("  " + Arrays.toString(fila));
IO.println("  m[1][2] = " + m[1][2] + "   filas " + m.length + "  columnas " + m[0].length);
salida[1, 2, 3]
  [4, 5, 6]
  m[1][2] = 6   filas 2  columnas 3
  deepToString: [[1, 2, 3], [4, 5, 6]]

En Java una matriz es un array de arrays, no una tabla. Eso significa que cada fila podría tener una longitud distinta, y que m[0].length es la longitud de la primera fila, no «el número de columnas» de nada.

Salir de un bucle

for (int i = 1; i <= 5; i++) {
    if (i == 3) continue;    // salta esta vuelta
    if (i == 5) break;       // abandona el bucle
    IO.print(i + " ");
}

buscar:
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (i * j == 2) { IO.println("encontrado en i=" + i + " j=" + j); break buscar; }
    }
}
salida1 2 4 
  encontrado en i=1 j=2

Lo de la etiqueta —buscar: y break buscar;— es lo más parecido a un goto que tiene Java, y existe por una razón concreta: salir de dos bucles anidados de golpe. Un break normal solo abandona el de dentro. Se usa poco, pero cuando hace falta, la alternativa es una variable de bandera que se lee peor.

Lo esencial

Para… Lo que se usa hoy
Elegir entre varios valores switch con ->, como expresión
Una rama de varias líneas llaves y yield
Distinguir por tipo case Tipo x ->, con when si hace falta
Recorrer for (T x : coleccion)
Tamaño array.length, cadena.length(), lista.size()
Comparar contenido equals / Arrays.equals, nunca ==
Imprimir un array Arrays.toString / deepToString
Salir de dos bucles una etiqueta y break etiqueta;

Todo el código de esta lección se compiló y se ejecutó con Java 25 LTS en un contenedor limpio antes de publicarla; las salidas y los errores del compilador están copiados de esa ejecución.