¿Por qué necesitamos un descanso después de las declaraciones de casos?

94

¿Por qué el compilador no coloca automáticamente declaraciones break después de cada bloque de código en el conmutador? ¿Es por razones históricas? ¿Cuándo desea que se ejecuten varios bloques de código?

unj2
fuente
2
Hice una respuesta sobre JDK-12 y se reformaron las etiquetas de los interruptores para no exigir break.
Naman

Respuestas:

94

A veces es útil tener varios casos asociados con el mismo bloque de código, como

case 'A':
case 'B':
case 'C':
    doSomething();
    break;

case 'D':
case 'E':
    doSomethingElse();
    break;

etc. Solo un ejemplo.

En mi experiencia, por lo general es de mal estilo "fallar" y ejecutar varios bloques de código para un caso, pero puede ser útil en algunas situaciones.

SalvajeCrustáceo
fuente
28
Solo agregue siempre un comentario a lo largo de las líneas // Intentional fallthrough.cuando omita un descanso. En mi opinión, no es tanto un mal estilo como "fácil olvidar una ruptura accidentalmente". PD: Por supuesto que no en casos simples como en la propia respuesta.
doublep
@doublep - estoy de acuerdo. En mi opinión, lo evitaría si fuera posible, pero si tiene sentido, asegúrese de que quede muy claro lo que está haciendo.
WildCrustacean
6
@doublep: No me molestaría con el comentario si los múltiples casese apilan juntos de esa manera. Si hay un código entre ellos, entonces sí, probablemente el comentario sea merecido.
Billy ONeal
4
Imagino un lenguaje en el que puedes declarar varios casos dentro de uno case, así:, case 'A','B','C': doSomething(); case 'D','E': doSomethingElse();sin necesidad de un descanso entre los casos. Pascal podría hacer eso: "La declaración del caso compara el valor de la expresión ordinal con cada selector, que puede ser una constante, un subrango o una lista de ellos separados por comas". ( wiki.freepascal.org/Case )
Christian Semrau
32

Históricamente , se debe a que caseesencialmente definía un label, también conocido como el punto de destino de una gotollamada. La declaración de cambio y sus casos asociados realmente representan una rama de múltiples vías con múltiples puntos de entrada potenciales en un flujo de código.

Dicho todo esto, se ha observado un número casi infinito de veces que breakcasi siempre es el comportamiento predeterminado que preferiría tener al final de cada caso.

Bob Cross
fuente
30

Java proviene de C y esa es la sintaxis de C.

Hay ocasiones en las que desea que varias declaraciones de casos solo tengan una ruta de ejecución. A continuación se muestra una muestra que le dirá cuántos días en un mes.

class SwitchDemo2 {
    public static void main(String[] args) {

        int month = 2;
        int year = 2000;
        int numDays = 0;

        switch (month) {
            case 1:
            case 3:
            case 5:
            case 7:
            case 8:
            case 10:
            case 12:
                numDays = 31;
                break;
            case 4:
            case 6:
            case 9:
            case 11:
                numDays = 30;
                break;
            case 2:
                if ( ((year % 4 == 0) && !(year % 100 == 0))
                     || (year % 400 == 0) )
                    numDays = 29;
                else
                    numDays = 28;
                break;
            default:
                System.out.println("Invalid month.");
                break;
        }
        System.out.println("Number of Days = " + numDays);
    }
}
Romain Hippeau
fuente
4
¿Alguien apuntó a la flecha hacia arriba y falló? O tal vez tuvieron un problema con su estilo de corsé o sangría ...
Jim Lewis
No sé, así que ten un +1 de mi parte. Este es un ejemplo en el que la falla ayuda, aunque realmente desearía que Java hubiera elegido una declaración de caso más moderna. EVALUATE-WHEN-OTHERWISE de Cobol es mucho más poderoso y es anterior a Java. La expresión de coincidencia de Scala es un ejemplo moderno de lo que se podría hacer.
Jim Ferrans
1
Mis alumnos serían azotados públicamente por hacer esto. Es coyote feo.
ncmathsadist
2
@ncmathsadist Ilustra un punto sobre una forma de hacer algo. No estoy en desacuerdo con que este ejemplo sea probablemente extremo. Pero es un ejemplo del mundo real, que creo que ayuda a las personas a comprender un concepto.
Romain Hippeau
15

Creo que es un error. Como construcción de lenguaje, es tan fácil de tener breakcomo predeterminado y, en su lugar, tener una fallthroughpalabra clave. La mayor parte del código que he escrito y leído tiene un descanso después de cada caso.

Presidente James K. Polk
fuente
4
Prefiero sugerir continue <case name>que permite especificar explícitamente con qué declaración de caso continuar;
Vilx-
4
@Vilx Al permitir un arbitrario casedentro de la corriente switch, esto simplemente se convierte en un goto. ;-)
Christian Semrau
13

Puede hacer todo tipo de cosas interesantes con la caída de casos.

Por ejemplo, digamos que desea realizar una acción en particular para todos los casos, pero en un caso determinado desea realizar esa acción y algo más. Usar una declaración de cambio con fallos lo haría bastante fácil.

switch (someValue)
{
    case extendedActionValue:
        // do extended action here, falls through to normal action
    case normalActionValue:
    case otherNormalActionValue:
        // do normal action here
        break;
}

Por supuesto, es fácil olvidar la breakdeclaración al final de un caso y provocar un comportamiento inesperado. Los buenos compiladores le advertirán cuando omita la declaración break.

Zach Johnson
fuente
¿Se puede usar switch / case en cadenas en Java?
Steve Kuo
@Steve: Vaya, supongo que no por el momento. Según stackoverflow.com/questions/338206/… , las cadenas se permitirán en una versión futura de Java. (Actualmente hago la mayor parte de mi programación en C #, lo que permite cadenas en las declaraciones de cambio). Edité la respuesta para eliminar las comillas engañosas.
Zach Johnson
2
@ZachJohnson, mucho más tarde, Java 7 permite activar Strings.
Bob Cross
7

¿Por qué el compilador no coloca automáticamente declaraciones break después de cada bloque de código en el conmutador?

Dejando a un lado las buenas ganas de poder utilizar el mismo bloque para varios casos (que podrían ser de carcasa especial) ...

¿Es por razones históricas? ¿Cuándo desea que se ejecuten varios bloques de código?

Es principalmente por compatibilidad con C, y podría decirse que es un antiguo truco de los días de antaño cuando las gotopalabras clave vagaban por la tierra. Se hace servir para que algunas cosas increíbles, por supuesto, como el dispositivo de Duff , pero si eso es un punto a su favor o en contra ... es argumentativo en el mejor.

Becarios Donal
fuente
5

El breakafter switch cases se usa para evitar errores en las declaraciones de switch. Aunque es interesante, esto ahora se puede lograr a través de las etiquetas de interruptor recién formadas implementadas a través de JEP-325 .

Con estos cambios, el breakcon cada interruptor casese puede evitar como se demuestra más adelante: -

public class SwitchExpressionsNoFallThrough {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int value = scanner.nextInt();
        /*
         * Before JEP-325
         */
        switch (value) {
            case 1:
                System.out.println("one");
            case 2:
                System.out.println("two");
            default:
                System.out.println("many");
        }

        /*
         * After JEP-325
         */
        switch (value) {
            case 1 ->System.out.println("one");
            case 2 ->System.out.println("two");
            default ->System.out.println("many");
        }
    }
}

En ejecutar el código anterior con JDK-12 , la salida comparativa podría verse como

//input
1
// output from the implementation before JEP-325
one
two
many
// output from the implementation after JEP-325
one

y

//input
2
// output from the implementation before JEP-325
two
many
// output from the implementation after JEP-325
two

y por supuesto la cosa sin cambios

// input
3
many // default case match
many // branches to 'default' as well
Naman
fuente
4

Por lo tanto, no tiene que repetir el código si necesita varios casos para hacer lo mismo:

case THIS:
case THAT:
{
    code;
    break;
}

O puedes hacer cosas como:

case THIS:
{
   do this;
}
case THAT:
{
   do that;
}

En forma de cascada.

Realmente propenso a errores / confusión, si me preguntas.

Francisco Soto
fuente
¿Eso funciona tanto para esto do thiscomo do thatpara eso, pero solo do thatpara eso?
JonnyRaa
1
solo lee los documentos. ¡Eso es horrible! ¡Qué manera más fácil de escribir errores!
JonnyRaa
4

En lo que respecta al registro histórico, Tony Hoare inventó el enunciado del caso en la década de 1960, durante la revolución de la "programación estructurada". La declaración de caso de Tony admitía múltiples etiquetas por caso y salida automática sin breakdeclaraciones apestosas . El requisito de un explícito breakfue algo que salió de la línea BCPL / B / C. Dennis Ritchie escribe (en ACM HOPL-II):

Por ejemplo, el caso final que se escapa de una declaración de conmutación BCPL no estaba presente en el lenguaje cuando lo aprendimos en la década de 1960, por lo que la sobrecarga de la palabra clave break para escapar de la declaración de conmutación B y C se debe a la evolución divergente en lugar de a la conciencia. cambio.

No he podido encontrar ningún escrito histórico sobre BCPL, pero el comentario de Ritchie sugiere que breakfue más o menos un accidente histórico. BCPL luego solucionó el problema, pero quizás Ritchie y Thompson estaban demasiado ocupados inventando Unix para molestarse con tal detalle :-)

Norman Ramsey
fuente
Esto debería obtener más votos. Aparentemente, el OP ya sabía que la omisión breakpermite "ejecutar múltiples bloques de código", y está más preocupado por la motivación de esta elección de diseño. Otros mencionaron la herencia bien conocida de C a Java, y esta respuesta llevó la investigación aún más a los días anteriores a C. Ojalá tuviéramos esta coincidencia de patrones (aunque muy primitiva) desde el principio.
wlnirvana
3

Java se deriva de C, cuya herencia incluye una técnica conocida como Dispositivo de Duff . Es una optimización que se basa en el hecho de que el control pasa de un caso a otro, en ausencia de una break;declaración. Para cuando C se estandarizó, había mucho código como ese "en la naturaleza", y hubiera sido contraproducente cambiar el lenguaje para romper tales construcciones.

Jim Lewis
fuente
1

Como la gente dijo antes, es para permitir la caída y no es un error, es una característica. Si le breakmolestan demasiadas declaraciones, puede deshacerse de ellas fácilmente utilizando returndeclaraciones en su lugar. En realidad, esta es una buena práctica, porque sus métodos deben ser lo más pequeños posible (en aras de la legibilidad y el mantenimiento), por lo que una switchdeclaración ya es lo suficientemente grande para un método, por lo tanto, un buen método no debe contener nada más, esto es un ejemplo:

public class SwitchTester{
    private static final Log log = LogFactory.getLog(SwitchTester.class);
    public static void main(String[] args){
        log.info(monthsOfTheSeason(Season.WINTER));
        log.info(monthsOfTheSeason(Season.SPRING));
        log.info(monthsOfTheSeason(Season.SUMMER));
        log.info(monthsOfTheSeason(Season.AUTUMN));
    }

    enum Season{WINTER, SPRING, SUMMER, AUTUMN};

    static String monthsOfTheSeason(Season season){
        switch(season){
            case WINTER:
                return "Dec, Jan, Feb";
            case SPRING:
                return "Mar, Apr, May";
            case SUMMER:
                return "Jun, Jul, Aug";
            case AUTUMN:
                return "Sep, Oct, Nov";
            default:
                //actually a NullPointerException will be thrown before reaching this
                throw new IllegalArgumentException("Season must not be null");
        }        
    }
}   

La ejecución imprime:

12:37:25.760 [main] INFO lang.SwitchTester - Dec, Jan, Feb
12:37:25.762 [main] INFO lang.SwitchTester - Mar, Apr, May
12:37:25.762 [main] INFO lang.SwitchTester - Jun, Jul, Aug
12:37:25.762 [main] INFO lang.SwitchTester - Sep, Oct, Nov

como se esperaba.

Víctor Gil
fuente
0

No tener una interrupción automática agregada por el compilador hace posible usar un interruptor / caso para probar condiciones como 1 <= a <= 3eliminar la declaración de interrupción de 1 y 2.

switch(a) {
  case 1: //I'm between 1 and 3
  case 2: //I'm between 1 and 3
  case 3: //I'm between 1 and 3
          break;
}
Soufiane Hassou
fuente
Yecch. Odio esto totalmente.
ncmathsadist
0

porque hay situaciones en las que desea fluir a través del primer bloque, por ejemplo, para evitar escribir el mismo código en varios bloques, pero aún así poder dividirlos para el control de mroe. También hay un montón de otras razones.

Jonas B
fuente
0

Es una vieja pregunta, pero en realidad me encontré usando el caso sin una declaración de interrupción hoy. En realidad, no utilizar break es muy útil cuando necesita combinar diferentes funciones en secuencia.

por ejemplo, usando códigos de respuesta http para autenticar al usuario con el token de tiempo

código de respuesta del servidor 401 - el token está desactualizado -> regenerar el token e iniciar la sesión del usuario.
código de respuesta del servidor 200 - el token está bien -> iniciar la sesión del usuario.

en declaraciones de casos:

case 404:
case 500:
        {
            Log.v("Server responses","Unable to respond due to server error");
            break;
        }
        case 401:
        {
             //regenerate token
        }
        case 200:
        {
            // log in user
            break;
        }

Con esto, no es necesario llamar a la función de inicio de sesión de usuario para la respuesta 401 porque cuando se regenera el token, el tiempo de ejecución salta al caso 200.

Vladimír Gašpar
fuente
0

Puede separar fácilmente otro tipo de número, mes, recuento.
Esto es mejor entonces si en este caso;

public static void spanishNumbers(String span){

    span = span.toLowerCase().replace(" ", "");
    switch (span){
     case "1":    
     case "jan":  System.out.println("uno"); break;    
     case "2":      
     case "feb":  System.out.println("dos"); break;    
     case "3":     
     case "mar":  System.out.println("tres"); break;   
     case "4":   
     case "apr":  System.out.println("cuatro"); break;
     case "5":    
     case "may":  System.out.println("cinco"); break;
     case "6":     
     case "jun":  System.out.println("seis"); break;
     case "7":    
     case "jul":  System.out.println("seite"); break;
     case "8":    
     case "aug":  System.out.println("ocho"); break;
     case "9":   
     case "sep":  System.out.println("nueve"); break;
     case "10":    
     case "oct": System.out.println("diez"); break;
     }
 }
John Dev
fuente
0

Ahora estoy trabajando en un proyecto donde lo necesito breaken mi declaración de cambio, de lo contrario, el código no funcionará. Tenga paciencia conmigo y le daré un buen ejemplo de por qué lo necesita breaken su declaración de cambio.

Imagine que tiene tres estados, uno que espera a que el usuario ingrese un número, el segundo para calcularlo y el tercero para imprimir la suma.

En ese caso tienes:

  1. Estado1 - Espere a que el usuario ingrese un número
  2. Estado2 - Imprime la suma
  3. state3 - Calcula la suma

Mirando los estados, querrá que el orden de exacción comience en state1 , luego state3 y finalmente state2 . De lo contrario, solo imprimiremos la entrada de los usuarios sin calcular la suma. Solo para aclararlo nuevamente, esperamos que el usuario ingrese un valor, luego calculamos la suma e imprime la suma.

Aquí hay un código de ejemplo:

while(1){
    switch(state){
      case state1:
        // Wait for user input code
        state = state3; // Jump to state3
        break;
      case state2:
        //Print the sum code
        state = state3; // Jump to state3;
      case state3:
        // Calculate the sum code
        state = wait; // Jump to state1
        break;
    }
}

Si no lo usamos break, se ejecutará en este orden, estado1 , estado2 y estado3 . Pero al usar break, evitamos este escenario y podemos ordenar el procedimiento correcto que es comenzar con state1, luego state3 y por último, pero no menos importante, state2.

Dler
fuente
-1

Exactamente, porque con una ubicación inteligente puedes ejecutar bloques en cascada.

Hellektor
fuente