Declarando e inicializando variáveis ​​em switches Java

99

Tenho uma pergunta maluca sobre os switches Java.

int key = 2;

switch (key) {
    case 1:
        int value = 1;
        break;
    case 2:
        value = 2;
        System.out.println(value);
        break;
    default:
        break;
}

Cenário 1 - Quando o keyé dois, ele imprime com sucesso o valor como 2.
Cenário 2 - Quando vou comentar value = 2, case 2:ele grita dizendo que o valor da variável local pode não ter sido inicializado .

Questões:

Cenário 1: Se o fluxo de execução não vai para case 1:(quando o key = 2), como ele sabe o tipo da variável de valor como int?

Cenário 2: Se o compilador conhece o tipo da variável de valor como int, então ele deve ter acessado a int value = 1;expressão em case 1:. (Declaração e Inicialização). Então, por que faz isso sqawrk Quando eu vou comentar value = 2no case 2:, dizendo que o O valor variável local pode não ter sido inicializado .

namalfernandolk
fonte
13
Não é uma pergunta maluca, é uma pergunta muito boa.
biziclop
Possível duplicata do escopo
Philippe Carriere
@PhilippeCarriere Na verdade, acho que deveria ser ao contrário - a resposta aqui é melhor (mesmo se a postagem for mais recente), pois há uma referência direta ao JLS e resume bem o assunto abordado em diferentes respostas nesse post. Veja também .
Tunaki
@Tunaki A descrição de uma duplicata começa com "Esta pergunta já foi feita antes". Estou lendo isso porque o último deve ser marcado como uma duplicata do anterior. Mas concordo que este tem bons elementos. Talvez eles devessem ser mesclados de alguma forma?
Philippe Carriere
Além disso, muitas perguntas sobre o SO estão marcadas como duplicatas da minha pergunta original, portanto, se você decidir que é melhor marcar esta como a nova original, corrija todos os links para fazer referência a este em vez do meu.
Philippe Carriere

Respostas:

114

As declarações switch são estranhas em termos de escopo, basicamente. Da seção 6.3 do JLS :

O escopo de uma declaração de variável local em um bloco (§14.4) é o resto do bloco no qual a declaração aparece, começando com seu próprio inicializador e incluindo quaisquer declaradores adicionais à direita na declaração de declaração de variável local.

No seu caso, case 2está no mesmo bloco que case 1e aparece depois dele, embora case 1nunca seja executado ... então a variável local está no escopo e disponível para escrita, apesar de você logicamente nunca "executar" a declaração. (Uma declaração não é realmente "executável", embora a inicialização seja.)

Se você comentar a value = 2;atribuição, o compilador ainda saberá a qual variável você está se referindo, mas você não terá passado por nenhum caminho de execução que atribua um valor a ela, e é por isso que você obtém um erro como faria quando tentasse leia qualquer outra variável local não definida definitivamente.

Eu recomendo fortemente que você não use variáveis ​​locais declaradas em outros casos - isso leva a um código altamente confuso, como você viu. Quando introduzo variáveis ​​locais em instruções switch (o que tento fazer raramente - os casos devem ser muito curtos, de preferência), geralmente prefiro introduzir um novo escopo:

case 1: {
    int value = 1;
    ...
    break;
}
case 2: {
    int value = 2;
    ...
    break;
}

Acredito que isso seja mais claro.

Jon Skeet
fonte
11
+1 para "Uma declaração não é realmente" executável ", embora a inicialização seja.". E obrigado pelos conselhos também Skeet.
namalfernandolk
1
Com o JEP-325 integrado, a imagem do escopo das variáveis ​​locais mudou e pode-se usar o mesmo nome em casos em vez de blocos de switch. Embora também dependa de codificação em bloco semelhante. Além disso, o valor atribuído a uma variável por caso de switch seria muito conveniente com as expressões de switch.
Naman,
Pontos para adicionar um novo escopo com colchetes. Eu nem sabia que você poderia fazer isso.
Floating Sunfish
21

A variável foi declarada (como um int), mas não inicializada (com um valor inicial atribuído). Pense na linha:

int value = 1;

Como:

int value;
value = 1;

A int valueparte diz ao compilador em tempo de compilação que você tem uma variável chamada valor que é um int. A value = 1parte o inicializa, mas isso acontece em tempo de execução e não acontece de forma alguma se esse branch do switch não for inserido.

Paulo
fonte
+1 para a bela explicação de declaração e inicialização em tempo de compilação e tempo de execução.
namalfernandolk
18

De http://www.coderanch.com/t/447381/java-programmer-SCJP/certification/variable-initialization-within-case-block

As declarações são processadas em tempo de compilação e não dependem do fluxo de execução do seu código. Uma vez que valueé declarado dentro do escopo local do bloco switch, ele pode ser usado em qualquer lugar daquele bloco a partir do ponto de sua declaração.

Lixo
fonte
1
por que esta resposta está sendo votada positivamente? não responde à pergunta, ao contrário da resposta de paul ou skeet ...
Dhruv Gairola
7
É verdade. Então, +1, um centavo, da minha parte também.
Ravinder Reddy
3

Com a integração de JEP 325: Switch Expressions (Preview) em compilações de acesso antecipado JDK-12. Existem certas mudanças que podem ser vistas na resposta de Jon -

  1. Escopo da variável local - As variáveis ​​locais nos casos de switch podem agora ser locais para o próprio caso em vez de todo o bloco de switch . Um exemplo (semelhante ao que Jon tentou sintaticamente também) considerando aDayclasse enum para obter mais explicações:

    public enum Day {
        MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
    }
    
    // some another method implementation
    Day day = Day.valueOf(scanner.next());
    switch (day) {
        case MONDAY,TUESDAY -> {
            var temp = "mon-tue";
            System.out.println(temp);
        }
        case WEDNESDAY,THURSDAY -> {
            var temp = Date.from(Instant.now()); // same variable name 'temp'
            System.out.println(temp);
        }
        default ->{
            var temp = 0.04; // different types as well (not mandatory ofcourse)
            System.out.println(temp);
        }
    }
  2. Switch Expressions - Se a intenção é atribuir um valor a uma variável e então fazer uso dele, uma vez pode fazer uso das expressões switch. por exemplo

    private static void useSwitchExpression() {
        int key = 2;
        int value = switch (key) {
            case 1 ->  1;
            case 2 -> 2;
            default -> {break 0;}
        };
        System.out.println("value = " + value); // prints 'value = 2'
    }
Naman
fonte
0

Esta explicação pode ajudar.

    int id=1;

    switch(id){
        default: 
            boolean b= false; // all switch scope going down, because there is no scope tag

        case 1:
            b = false;
        case 2:{
            //String b= "test"; you can't declare scope here. because it's in the scope @top
            b=true; // b is still accessible
        }
        case 3:{
            boolean c= true; // case c scope only
            b=true; // case 3 scope is whole switch
        }
        case 4:{
            boolean c= false; // case 4 scope only
        }
    }
Java jansen
fonte
0

Especificação Java:

https://docs.oracle.com/javase/specs/jls/se12/html/jls-14.html#jls-14.11

O caso de conclusão abrupta por causa de uma quebra com um rótulo é tratado pela regra geral para declarações rotuladas (§14.7).

https://docs.oracle.com/javase/specs/jls/se12/html/jls-14.html#jls-14.7

Declarações rotuladas:

LabeledStatement: Identifier: Statement

LabeledStatementNoShortIf: Identificador: StatementNoShortIf

Ao contrário de C e C ++, a linguagem de programação Java não tem instrução goto; rótulos de declaração de identificador são usados ​​com declarações break (§14.15) ou continue (§14.16) que aparecem em qualquer lugar dentro da declaração rotulada.

O escopo de um rótulo de uma instrução rotulada é a Instrução contida imediatamente.

Em outras palavras, caso 1, caso 2 são rótulos dentro da instrução switch. as instruções break e continue podem ser aplicadas aos rótulos.

Como os rótulos compartilham o escopo da instrução, todas as variáveis ​​definidas dentro dos rótulos compartilham o escopo da instrução switch.

Pavel Molchanov
fonte