5 minutos

Declaración Switch en Java

1. Introducción

En este tutorial, aprenderemos qué es la declaración switch y cómo utilizarla.

La declaración switch nos permite reemplazar varias construcciones if-else anidadas y, por lo tanto, mejorar la legibilidad de nuestro código.

Switch ha evolucionado con el tiempo. Se han agregado nuevos tipos admitidos, especialmente en Java 5 y 7. Además, en Java 14 se introdujeron las expresiones switch.

A continuación, daremos algunos ejemplos de código para demostrar el uso de la declaración switch, el papel de la declaración break, los requisitos para los argumentos case y la comparación de strings en una declaración switch.

Pasemos al ejemplo.

2. Ejemplo de Uso

Supongamos que tenemos las siguientes declaraciones if-else anidadas:

public String ejemploDeIf(String animal) {
    String resultado;
    if (animal.equals("PERRO") || animal.equals("GATO")) {
        resultado = "animal doméstico";
    } else if (animal.equals("TIGRE")) {
        resultado = "animal salvaje";
    } else {
        resultado = "animal desconocido";
    }
    return resultado;
}

El código anterior no luce bien y sería difícil de mantener y comprender.

Para mejorar la legibilidad, podríamos utilizar una declaración switch:

public String ejemploDeSwitch(String animal) {
    String resultado;
    switch (animal) {
        case "PERRO":
            resultado = "animal doméstico"; 
            break;
        case "GATO":
            resultado = "animal doméstico";
            break;
        case "TIGRE":
            resultado = "animal salvaje";
            break;
        default:
            resultado = "animal desconocido";
            break;
    }
    return resultado;
}

Comparamos el argumento switch animal con varios valores de caso (case). Si ninguno de los valores de caso es igual al argumento, se ejecuta el bloque bajo la etiqueta predeterminada (default).

En pocas palabras, la declaración break se utiliza para salir de una declaración switch.

3. La Declaración break

Aunque la mayoría de las declaraciones switch en la vida real implican que solo se debe ejecutar uno de los bloques case, la declaración break es necesaria para salir de un switch después de que se complete el bloque.

Si olvidamos escribir un break, se ejecutarán los bloques que estén debajo.

Para demostrar esto, omitamos las declaraciones break y agreguemos la salida a la consola para cada bloque:

public String olvidarBreakEnSwitch(String animal) {
    switch (animal) {
    case "PERRO":
        System.out.println("animal doméstico");
    default:
        System.out.println("animal desconocido");
    }
}

Ejecutemos este código olvidarBreakEnSwitch("PERRO") y verifiquemos la salida para demostrar que se ejecutan todos los bloques:

animal doméstico
animal desconocido

Por lo tanto, debemos tener cuidado y agregar declaraciones break al final de cada bloque a menos que sea necesario pasar al código bajo la siguiente etiqueta.

El único bloque donde no es necesario un break es el último, pero agregar un break al último bloque hace que el código sea menos propenso a errores.

También podemos aprovechar este comportamiento para omitir el break cuando deseamos que se ejecute el mismo código para varios casos.

Reescribamos el ejemplo en la sección anterior agrupando los dos primeros casos:

public String ejemploDeSwitch(String animal) {
    String resultado;
    switch (animal) {
        case "PERRO":
        case "GATO":
            resultado = "animal doméstico";
            break;
        case "TIGRE":
            resultado = "animal salvaje";
            break;
        default:
            resultado = "animal desconocido";
            break;
    }
    return resultado;
}

4. Argumento de switch y Valores de case

Ahora hablemos de los tipos permitidos para el argumento de switch y los valores de caso, los requisitos para ellos y cómo funciona la declaración switch con cadenas.

4.1. Tipos de Datos

No podemos comparar todos los tipos de objetos y primitivas en la declaración switch. Un switch solo funciona con cuatro primitivas y sus objetos envoltorios (wrappers), así como con el tipo enum y la clase String:

  • byte y Byte
  • short y Short
  • int y Integer
  • char y Character
  • enum
  • String

El tipo String está disponible en la declaración switch a partir de Java 7.

El tipo enum se introdujo en Java 5 y ha estado disponible en la declaración switch desde entonces.

Las clases envoltorias también han estado disponibles desde Java 5.

Por supuesto, el argumento de switch y los valores case deben ser del mismo tipo.

4.2. No Valores null

No podemos pasar el valor null como argumento a una declaración switch.

Si lo hacemos, el programa lanzará NullPointerException, usando nuestro primer ejemplo de switch:

@Test(expected=NullPointerException.class)
public void cuandoArgumentoSwitchEsNull_entoncesNPE() {
    String animal = null;
    Assert.assertEquals("animal doméstico", s.ejemploDeSwitch(animal));
}

Por supuesto, tampoco podemos pasar null como valor a la etiqueta case de una declaración switch. Si lo hacemos, el código no se compilará.

4.3. Valores case como Constantes en Tiempo de Compilación

Si intentamos reemplazar el valor de caso PERRO con la variable perro, el código no se compilará a menos que marquemos la variable perro como final:

final String perro = "PERRO";
String gato = "GATO";

switch (animal) {
case perro: //compila
    resultado = "animal doméstico";
case gato: //no compila
    resultado = "felino";
}

4.4. Comparación de Strings

Si una declaración switch utilizara el operador de igualdad para comparar cadenas, no podríamos comparar correctamente un argumento String creado con el operador new con un valor de caso String.

Afortunadamente, el operador switch utiliza el método equals() en segundo plano.

Demostrémoslo:

@Test
public void cuandoComparamosStrings_entoncesPorEquals() {
    String animal = new String("PERRO");
    assertEquals("animal doméstico", s.ejemploDeSwitch(animal));
}

5. Expresiones switch

JDK 14 ya está disponible y trae una versión mejorada de una nueva característica que se introdujo por primera vez en JDK 12: la expresión switch.

5.1. La Nueva Expresión switch

Veamos cómo se ve la nueva expresión switch cuando cambiamos de meses:

var resultado = switch(mes) {
    case ENERO, JUNIO, JULIO -> 3;
    case FEBRERO, SEPTIEMBRE, OCTUBRE, NOVIEMBRE, DICIEMBRE -> 1;
    case MARZO, MAYO, ABRIL, AGOSTO -> 2;
    default -> 0; 
};

Enviar un valor como Meses.JUNIO establecería el resultado en 3.

Observa que la nueva sintaxis utiliza el operador -> en lugar de los dos puntos a los que estamos acostumbrados con las declaraciones switch. Además, no hay una palabra clave break: la expresión switch no pasa a través de los cases.

Otra adición es que ahora podemos tener criterios delimitados por comas.

5.2. La Palabra Clave yield

Yendo un poco más allá, existe la posibilidad de obtener un control detallado sobre lo que sucede en el lado derecho de la expresión utilizando bloques de código.

En ese caso, debemos utilizar la palabra clave yield:

var resultado = switch (mes) {
    case ENERO, JUNIO, JULIO -> 3;
    case FEBRERO, SEPTIEMBRE, OCTUBRE, NOVIEMBRE, DICIEMBRE -> 1;
    case MARZO, MAYO, ABRIL, AGOSTO -> {
        int longitudDelMes = mes.toString().length();
        yield longitudDelMes * 4;
    }
    default -> 0;
};

Aunque nuestro ejemplo es un tanto arbitrario, el punto es que tenemos acceso a más características del lenguaje Java aquí.

5.3. Retorno Dentro de las Expresiones switch

Como consecuencia de la distinción entre las declaraciones switch y las expresiones switch, es posible usar return desde dentro de una declaración switch, pero no se nos permite hacerlo desde una expresión switch.

El siguiente ejemplo es perfectamente válido y se compilará:

switch (mes) {
    case ENERO, JUNIO, JULIO -> { return 3; }
    default -> { return 0; }
}

Sin embargo, el siguiente código no se compilará, ya que estamos tratando de usar return fuera de una expresión switch que lo englobe:

var resultado = switch (mes) {
    case ENERO, JUNIO, JULIO -> { return 3; }
    default -> { return 0; }
};

5.4. Exhaustividad

Cuando utilizamos declaraciones switch, no importa si se cubren todos los casos.

El siguiente código, por ejemplo, es perfectamente válido y se compilará:

switch (mes) { 
    case ENERO, JUNIO, JULIO -> 3; 
    case FEBRERO, SEPTIEMBRE -> 1;
}

Sin embargo, para las expresiones switch, el compilador insiste en que se cubran todos los casos posibles.

El siguiente fragmento de código no se compilaría, ya que no hay un caso predeterminado y no se cubren todos los casos posibles:

var resultado = switch (mes) {
    case ENERO, JUNIO, JULIO -> 3;
    case FEBRERO, SEPTIEMBRE -> 1;
}

La expresión switch, sin embargo, será válida cuando se cubran todos los casos posibles:

var resultado = switch (mes) {
    case ENERO, JUNIO, JULIO -> 3;
    case FEBRERO, SEPTIEMBRE, OCTUBRE, NOVIEMBRE, DICIEMBRE -> 1;
    case MARZO, MAYO, ABRIL, AGOSTO -> 2;
}

Ten en cuenta que el fragmento de código anterior no tiene un caso default. Siempre y cuando se cubran todos los casos, la expresión switch será válida.

6. Conclusión

En este artículo, discutimos las sutilezas de usar la declaración switch en Java. Podemos decidir si usar switch en función de la legibilidad y el tipo de valores que se comparan.

La declaración switch es una buena opción para casos en los que tenemos un número limitado de opciones en un conjunto predefinido (por ejemplo, los días de la semana).

De lo contrario, tendríamos que modificar el código cada vez que se agregue o elimine un nuevo valor, lo cual puede no ser factible. Para estos casos, podríamos considerar otro enfoque como el polimorfismo.