Por que precisamos de uma pausa após as declarações de caso?

94

Por que o compilador não coloca automaticamente as instruções break após cada bloco de código no switch? É por razões históricas? Quando você deseja que vários blocos de código sejam executados?

unj2
fonte
2
Respondeu sobre o JDK-12 e mudou as etiquetas para não obrigar break.
Naman,

Respostas:

94

Às vezes é útil ter vários casos associados ao mesmo bloco de código, como

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

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

etc. Apenas um exemplo.

Na minha experiência, geralmente é um estilo ruim "falhar" e ter vários blocos de código executados para um caso, mas pode haver usos para isso em algumas situações.

WildCrustacean
fonte
28
Basta sempre adicionar um comentário ao longo das linhas // Intentional fallthrough.ao omitir uma pausa. Não é tanto um estilo ruim quanto "fácil de esquecer uma pausa acidentalmente" na minha opinião. PS Claro que não em casos simples como na própria resposta.
doublep
@doublep - eu concordo. Na minha opinião, eu o evitaria, se possível, mas se fizer sentido, certifique-se de que está bem claro o que você está fazendo.
WildCrustacean
6
@doublep: Eu não me importaria com o comentário se os vários cases estivessem empilhados dessa forma. Se houver código entre eles, então sim, o comentário provavelmente é válido.
Billy ONeal
4
Eu imagino uma linguagem onde você possa declarar vários casos dentro de um case, assim case 'A','B','C': doSomething(); case 'D','E': doSomethingElse();:, sem precisar de uma pausa entre os casos. Pascal poderia fazer isso: "A instrução case compara o valor da expressão ordinal a cada seletor, que pode ser uma constante, um subintervalo ou uma lista deles separados por vírgulas." ( wiki.freepascal.org/Case )
Christian Semrau
32

Historicamente , é porque o caseestava essencialmente definindo um label, também conhecido como o ponto-alvo de uma gotochamada. A instrução switch e seus casos associados representam apenas uma ramificação de múltiplas vias com vários pontos de entrada potenciais em um fluxo de código.

Dito isso, foi observado um número quase infinito de vezes que breakquase sempre é o comportamento padrão que você prefere ter no final de cada caso.

Bob Cross
fonte
30

Java vem de C e essa é a sintaxe de C.

Há momentos em que você deseja que várias instruções de caso tenham apenas um caminho de execução. Abaixo está um exemplo que dirá quantos dias em um mês.

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
fonte
4
Alguém mirou na seta para cima e errou? Ou talvez eles tenham um problema com o seu estilo de suporte ou recuo ...
Jim Lewis
Não sei, então receba um +1 meu. Este é um exemplo em que a falha ajuda, embora eu realmente desejasse que o Java tivesse escolhido uma instrução case mais moderna. EVALUATE-WHEN-OTHERWISE do Cobol é muito mais poderoso e é anterior ao Java. A expressão de correspondência de Scala é um exemplo moderno do que poderia ser feito.
Jim Ferrans,
1
Meus alunos seriam açoitados publicamente por fazer isso. É um coiote feio.
ncmathsadist
2
@ncmathsadist Ilustra um ponto sobre uma maneira de fazer algo. Não discordo que este exemplo seja provavelmente extremo. Mas é um exemplo do mundo real, que acredito ajuda as pessoas a compreenderem um conceito.
Romain Hippeau,
15

Eu acho que é um erro. Como uma construção de linguagem é tão fácil de ter breakquanto o padrão e, em vez disso, ter uma fallthroughpalavra - chave. A maior parte do código que escrevi e li tem uma pausa após cada caso.

Presidente James K. Polk
fonte
4
Eu prefiro sugerir o continue <case name>que permite especificar explicitamente com qual instrução de caso continuar;
Vilx
4
@Vilx Ao permitir um arbitrário casedentro da corrente switch, isso simplesmente se torna um goto. ;-)
Christian Semrau
13

Você pode fazer todo tipo de coisa interessante com o caso de falha.

Por exemplo, digamos que você queira fazer uma ação específica para todos os casos, mas em um determinado caso você deseja fazer essa ação mais alguma outra coisa. Usar uma instrução switch com fall-through tornaria isso muito fácil.

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

Obviamente, é fácil esquecer a breakdeclaração no final de um caso e causar um comportamento inesperado. Bons compiladores irão avisá-lo quando você omitir a instrução break.

Zach Johnson
fonte
O switch / case pode ser usado em strings em Java?
Steve Kuo,
@Steve: Opa, acho que não no momento. De acordo com stackoverflow.com/questions/338206/… , strings serão permitidas em uma versão futura do Java. (Atualmente faço a maior parte da minha programação em C #, o que permite strings em instruções switch.) Editei a resposta para remover as aspas enganosas.
Zach Johnson
2
@ZachJohnson, muito mais tarde, Java 7 permite a ativação de Strings.
Bob Cross
7

Por que o compilador não coloca automaticamente as instruções break após cada bloco de código no switch?

Deixando de lado a boa vontade de poder usar o bloco idêntico para vários casos (que poderiam ser especiais) ...

É por razões históricas? Quando você deseja que vários blocos de código sejam executados?

É principalmente para compatibilidade com C, e é indiscutivelmente um hack antigo dos tempos antigos, quando as gotopalavras-chave vagavam pela terra. Ele faz permitir que algumas coisas surpreendentes, é claro, como o dispositivo de Duff , mas se isso é um ponto a seu favor ou contra é ... argumentativa na melhor das hipóteses.

Donal Fellows
fonte
5

O breakswitch posterior cases é usado para evitar a queda nas instruções switch. Embora seja interessante, isso agora pode ser alcançado por meio dos rótulos de switch recém-formados, implementados via JEP-325 .

Com essas mudanças, o breakcom cada troca casepode ser evitado conforme demonstrado mais adiante: -

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");
        }
    }
}

Ao executar o código acima com JDK-12 , a saída comparativa pode ser vista como

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

e

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

e, claro, a coisa inalterada

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

Portanto, você não precisa repetir o código se precisar de vários casos para fazer a mesma coisa:

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

Ou você pode fazer coisas como:

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

Em forma de cascata.

Muito sujeito a erros / confusão, se você me perguntar.

Francisco Soto
fonte
faz esse prazo tanto do thise do thatpara isso, mas apenas do thatpara isso?
JonnyRaa
1
apenas leia os documentos. Isso é horrível! Que maneira fácil de escrever bugs!
JonnyRaa
4

No que diz respeito ao registro histórico, Tony Hoare inventou a declaração do caso na década de 1960, durante a revolução da "programação estruturada". A declaração de caso de Tony suportava vários rótulos por caso e saída automática sem breakdeclarações fedorentas . A exigência de um explícito breakfoi algo que saiu da linha BCPL / B / C. Dennis Ritchie escreve (em ACM HOPL-II):

Por exemplo, o endcase que escapa de uma instrução switchon BCPL não estava presente na linguagem quando a aprendemos na década de 1960 e, portanto, a sobrecarga da palavra-chave break para escapar da instrução switch B e C deve-se à evolução divergente em vez de consciente mudança.

Não consegui encontrar nenhum escrito histórico sobre a BCPL, mas o comentário de Ritchie sugere que breakfoi mais ou menos um acidente histórico. O BCPL mais tarde corrigiu o problema, mas talvez Ritchie e Thompson estivessem ocupados demais inventando o Unix para se incomodar com tal detalhe :-)

Norman Ramsey
fonte
Isso deve gerar mais votos. Aparentemente, o OP já sabia que omitir breakpermite "executar vários blocos de código", e está mais preocupado com a motivação dessa escolha de design. Outros mencionaram a herança bem conhecida do C ao Java, e essa resposta empurrou a pesquisa ainda mais para os dias anteriores ao C. Eu gostaria que tivéssemos esse padrão (embora muito primitivo) correspondendo desde o início.
wlnirvana
3

Java é derivado de C, cuja herança inclui uma técnica conhecida como Dispositivo de Duff . É uma otimização que depende do fato de que o controle cai de um caso para o outro, na ausência de uma break;instrução. Na época em que C foi padronizado, havia muito código como aquele "em liberdade", e teria sido contraproducente alterar a linguagem para quebrar tais construções.

Jim Lewis
fonte
1

Como já se disse antes, é para permitir falhas e não é um erro, é uma característica. Se muitas breakdeclarações o incomodam, você pode facilmente se livrar delas usando as returndeclarações. Na verdade, esta é uma boa prática, porque seus métodos devem ser os menores possíveis (por uma questão de legibilidade e manutenção), então uma switchinstrução já é grande o suficiente para um método, portanto, um bom método não deve conter nada mais, isso é um exemplo:

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");
        }        
    }
}   

A execução 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 esperado.

Víctor Gil
fonte
0

Não ter uma interrupção automática adicionada pelo compilador torna possível usar uma opção / caso para testar as condições, como 1 <= a <= 3remover a instrução break de 1 e 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
fonte
Yecch. Eu odeio totalmente isso.
ncmathsadist
0

porque há situações em que você deseja fluir através do primeiro bloco, por exemplo, para evitar escrever o mesmo código em vários blocos, mas ainda ser capaz de dividi-los para controle mroe. Existem também muitos outros motivos.

Jonas B
fonte
0

É uma pergunta antiga, mas na verdade eu me deparei com a instrução case without break hoje. Na verdade, não usar break é muito útil quando você precisa combinar diferentes funções em sequência.

por exemplo, usando códigos de resposta http para autenticar o usuário com token de tempo

código de resposta do servidor 401 - token desatualizado -> regenerar token e fazer login do usuário.
código de resposta do servidor 200 - token está OK -> fazer login do usuário.

em declarações de caso:

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;
        }

Usando isso, você não precisa chamar a função de usuário de log para a resposta 401 porque quando o token é regenerado, o tempo de execução pula para o caso 200.

Vladimír Gašpar
fonte
0

Você pode fazer facilmente separar outro tipo de número, mês, contagem.
Isso é melhor do que neste 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
fonte
0

Agora estou trabalhando em um projeto do qual preciso, breakem minha instrução switch, caso contrário, o código não funcionará. Tenha paciência comigo e eu darei um bom exemplo de por que você precisa breakem sua declaração switch.

Imagine que você tenha três estados, um que espera que o usuário insira um número, o segundo para calculá-lo e o terceiro para imprimir a soma.

Nesse caso, você tem:

  1. Estado1 - aguarde até que o usuário insira um número
  2. Estado2 - Imprimir a soma
  3. state3 - Calcule a soma

Olhando para os estados, você gostaria que a ordem de exação começasse no estado1 , depois no estado3 e finalmente no estado2 . Caso contrário, imprimiremos apenas as entradas dos usuários sem calcular a soma. Só para esclarecer novamente, esperamos que o usuário insira um valor, então calculamos a soma e imprime a soma.

Aqui está um exemplo de código:

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;
    }
}

Se não usarmos break, ele será executado nesta ordem, estado1 , estado2 e estado3 . Mas usando break, evitamos este cenário e podemos solicitar o procedimento correto que deve começar com o estado1, depois o estado3 e, por último, mas não menos importante, o estado2.

Dler
fonte
-1

Exatamente, porque com algum posicionamento inteligente você pode executar blocos em cascata.

Hellektor
fonte