Déclaration et initialisation de variables dans les commutateurs Java

99

J'ai une question folle sur les commutateurs Java.

int key = 2;

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

Scénario 1 - Lorsque le keyest deux , il imprime avec succès la valeur 2.
Scénario 2 - Quand je vais commenter value = 2dans case 2:ce gloussements dire la La valeur variable locale peut ne pas avoir été initialisé .

Questions:

Scénario 1: Si le flux d'exécution ne va pas à case 1:(quand le key = 2), alors comment connaît-il le type de la variable de valeur int?

Scénario 2: Si le compilateur connaît le type de la variable de valeur comme int, alors il doit avoir accédé à l' int value = 1;expression dans case 1:. (Déclaration et initialisation). Alors pourquoi est - ce sqawrk Quand je vais commenter value = 2en case 2:disant la La valeur variable locale peut ne pas avoir été initialisé .

Namalfernandolk
la source
13
Ce n'est pas une question folle, c'est une très bonne question.
biziclop
Possible duplicata de la portée
Philippe Carriere
@PhilippeCarriere En fait, je pense que cela devrait être inversé - la réponse ici est meilleure (même si le message est plus récent) car il y a une référence directe au JLS, et résume bien le problème couvert dans différentes réponses dans cet article. Voir aussi .
Tunaki
@Tunaki La description d'un doublon commence par "Cette question a déjà été posée". Je lis cela car le dernier devrait être marqué comme un double du précédent. Mais je suis d'accord que celui-ci a de beaux éléments. Peut-être devraient-ils être fusionnés d'une manière ou d'une autre?
Philippe Carriere
De plus, beaucoup de questions sur SO sont marquées comme des doublons de ma question originale, donc si vous décidez qu'il vaut mieux marquer celle-ci comme la nouvelle original, veuillez corriger tous les liens pour faire référence à celle-ci au lieu de la mienne.
Philippe Carriere

Réponses:

114

Les instructions de commutation sont étranges en termes de portée, en gros. À partir de la section 6.3 du JLS :

La portée d'une déclaration de variable locale dans un bloc (§14.4) est le reste du bloc dans lequel la déclaration apparaît, en commençant par son propre initialiseur et en incluant tout autre déclarateur à droite dans l'instruction de déclaration de variable locale.

Dans votre cas, case 2est dans le même bloc que case 1et apparaît après, même si case 1elle ne s'exécutera jamais ... donc la variable locale est dans la portée et disponible pour l' écriture même si vous n'avez logiquement jamais "exécuté" la déclaration. (Une déclaration n'est pas vraiment "exécutable" bien que l'initialisation le soit.)

Si vous commentez l' value = 2;affectation, le compilateur sait toujours à quelle variable vous faites référence, mais vous n'aurez parcouru aucun chemin d'exécution qui lui attribue une valeur, c'est pourquoi vous obtenez une erreur comme vous le feriez lorsque vous essayez de lire toute autre variable locale non attribuée de manière définitive.

Je vous recommande fortement de ne pas utiliser de variables locales déclarées dans d'autres cas - cela conduit à un code très déroutant, comme vous l'avez vu. Lorsque j'introduis des variables locales dans des instructions switch (ce que j'essaie de faire rarement - les cas devraient être très courts, idéalement), je préfère généralement introduire une nouvelle portée:

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

Je pense que c'est plus clair.

Jon Skeet
la source
11
+1 pour "Une déclaration n'est pas vraiment" exécutable "bien que l'initialisation le soit.". Et merci pour les conseils aussi Skeet.
namalfernandolk
1
Avec le JEP-325 intégré, l' image de la portée des variables locales a changé et on peut utiliser le même nom dans les cas au lieu des blocs de commutation. Bien qu'il repose également sur un codage de bloc similaire. En outre, la valeur attribuée à une variable par cas de commutateur serait très pratique avec les expressions de commutateur.
Naman
Points pour ajouter une nouvelle portée avec des accolades. Je ne savais même pas que tu pouvais faire ça.
Poisson-soleil flottant du
21

La variable a été déclarée (en tant qu'int), mais pas initialisée (une valeur initiale lui a été attribuée). Pensez à la ligne:

int value = 1;

Comme:

int value;
value = 1;

La int valuepartie indique au compilateur au moment de la compilation que vous avez une variable appelée value qui est un int. La value = 1partie l'initialise, mais cela se produit au moment de l'exécution et ne se produit pas du tout si cette branche du commutateur n'est pas entrée.

Paul
la source
+1 pour la belle explication de la déclaration et de l'initialisation au moment de la compilation et à l'exécution.
namalfernandolk
18

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

Les déclarations sont traitées au moment de la compilation et ne dépendent pas du flux d'exécution de votre code. Comme il valueest déclaré dans la portée locale du bloc de commutation, il est utilisable n'importe où dans ce bloc à partir du point de sa déclaration.

Des ordures
la source
1
pourquoi cette réponse est-elle votée? il ne répond pas à la question, contrairement à la réponse de paul ou skeet ...
Dhruv Gairola
7
Cela fait. Donc, +1, un sou, de mon côté aussi.
Ravinder Reddy
3

Avec l'intégration de JEP 325: Switch Expressions (Preview) dans les versions d'accès anticipé JDK-12. Il y a certains changements qui pourraient être vus de la réponse de Jon -

  1. Portée de la variable locale - Les variables locales dans les cas de commutation peuvent désormais être locales au cas lui-même au lieu de l'ensemble du bloc de commutation . Un exemple (similaire à ce que Jon avait tenté syntaxiquement également) considérant laDayclasse enum pour plus d'explications:

    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 - Si l'intention est d'attribuer une valeur à une variable puis de l'utiliser, une fois peut utiliser les expressions switch. par exemple

    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
la source
0

Cette explication pourrait vous aider.

    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
la source
0

Spécification Java:

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

Le cas d'un achèvement brutal dû à une rupture avec une étiquette est traité par la règle générale des instructions étiquetées (§14.7).

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

Déclarations étiquetées:

LabeledStatement: Identificateur: Instruction

LabeledStatementNoShortIf: Identificateur: StatementNoShortIf

Contrairement à C et C ++, le langage de programmation Java n'a pas d'instruction goto; Les étiquettes d'instructions d'identificateur sont utilisées avec des instructions break (§14.15) ou continue (§14.16) apparaissant n'importe où dans l'instruction étiquetée.

La portée d'une étiquette d'une instruction étiquetée est la déclaration immédiatement contenue.

En d'autres termes, le cas 1, le cas 2 sont des étiquettes dans l'instruction switch. Les instructions break et continue peuvent être appliquées aux étiquettes.

Étant donné que les étiquettes partagent la portée de l'instruction, toutes les variables définies dans les étiquettes partagent la portée de l'instruction switch.

Pavel Molchanov
la source