Deklarieren und Initialisieren von Variablen in Java-Switches

99

Ich habe eine verrückte Frage zu Java-Switches.

int key = 2;

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

Szenario 1 - Wenn die keybeiden ist es erfolgreich den Wert als 2. drucken
Szenario 2 - Wenn ich Kommentar werde value = 2in case 2:es die kreischt sagen Die lokale Variable Wert nicht initialisiert worden sein .

Fragen:

Szenario 1: Wenn der Ausführungsfluss nicht zu case 1:(wann key = 2) wechselt, woher kennt er dann den Typ der Wertvariablen als int?

Szenario 2: Wenn der Compiler den Typ der Wertvariablen als kennt int, muss er auf den int value = 1;Ausdruck in zugegriffen haben case 1:(Deklaration und Initialisierung). Warum tut es sqawrk Wenn ich werde Kommentar value = 2in case 2:die sagen , die lokale Variable Wert kann nicht initialisiert wurden .

namalfernandolk
quelle
13
Es ist keine verrückte Frage, es ist eine sehr gute Frage.
Biziclop
Mögliches Duplikat des Gültigkeitsbereichs
Philippe Carriere
@PhilippeCarriere Eigentlich denke ich, dass es umgekehrt sein sollte - die Antwort hier ist besser (auch wenn der Beitrag neuer ist), da es einen direkten Verweis auf das JLS gibt, und fasst das Problem, das in verschiedenen Antworten in diesem Beitrag behandelt wird, gut zusammen. Siehe auch .
Tunaki
@Tunaki Die Beschreibung für ein Duplikat beginnt mit "Diese Frage wurde bereits gestellt". Ich lese, dass das spätere als Duplikat des früheren markiert werden sollte. Aber ich stimme zu, dass dieser schöne Elemente hat. Vielleicht sollten sie irgendwie zusammengeführt werden?
Philippe Carriere
Außerdem sind viele Fragen zu SO als Duplikat meiner ursprünglichen Frage markiert. Wenn Sie also entscheiden, dass es besser ist, diese Frage als neues Original zu markieren, korrigieren Sie bitte alle Links, um auf diese anstelle meiner zu verweisen.
Philippe Carriere

Antworten:

114

Switch-Anweisungen sind im Hinblick auf den Umfang grundsätzlich ungerade. Aus Abschnitt 6.3 des JLS :

Der Umfang einer lokalen Variablendeklaration in einem Block (§14.4) ist der Rest des Blocks, in dem die Deklaration angezeigt wird, beginnend mit einem eigenen Initialisierer und einschließlich aller weiteren Deklaratoren rechts in der lokalen Variablendeklarationsanweisung.

In Ihrem Fall case 2befindet es sich im selben Block wie case 1und wird danach angezeigt, obwohl case 1es niemals ausgeführt wird. Die lokale Variable befindet sich also im Gültigkeitsbereich und kann geschrieben werden, obwohl Sie die Deklaration logischerweise nie "ausführen". (Eine Deklaration ist nicht wirklich "ausführbar", obwohl die Initialisierung erfolgt.)

Wenn Sie die value = 2;Zuweisung auskommentieren, weiß der Compiler immer noch, auf welche Variable Sie sich beziehen, aber Sie haben keinen Ausführungspfad durchlaufen, der ihr einen Wert zuweist. Aus diesem Grund erhalten Sie wie beim Versuch einen Fehler Lesen Sie jede andere nicht definitiv zugewiesene lokale Variable.

Ich würde Ihnen dringend empfehlen, keine in anderen Fällen deklarierten lokalen Variablen zu verwenden - dies führt, wie Sie gesehen haben, zu höchst verwirrendem Code. Wenn ich lokale Variablen in switch-Anweisungen einführe (was ich selten versuche - Fälle sollten im Idealfall sehr kurz sein), bevorzuge ich normalerweise die Einführung eines neuen Bereichs:

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

Ich glaube das ist klarer.

Jon Skeet
quelle
11
+1 für "Eine Deklaration ist nicht wirklich" ausführbar ", obwohl die Initialisierung ist." Und danke auch für die Ratschläge Skeet.
Namalfernandolk
1
Mit dem integrierten JEP-325 hat sich das Bild des Umfangs lokaler Variablen geändert, und man kann in allen Fällen denselben Namen anstelle von Schaltblöcken verwenden. Es basiert jedoch auch auf einer ähnlichen Blockcodierung. Außerdem wäre der Wert, der einer Variablen pro Schalterfall zugewiesen wird, mit den Schalterausdrücken sehr praktisch.
Naman
Punkte zum Hinzufügen eines neuen Bereichs mit geschweiften Klammern. Ich wusste nicht einmal, dass du das kannst.
Floating Sunfish
21

Die Variable wurde deklariert (als int), aber nicht initialisiert (mit einem Anfangswert versehen). Denken Sie an die Linie:

int value = 1;

Wie:

int value;
value = 1;

Der int valueTeil teilt dem Compiler zur Kompilierungszeit mit, dass Sie eine Variable namens value haben, die ein int ist. Der value = 1Teil initialisiert es, aber das passiert zur Laufzeit und überhaupt nicht, wenn dieser Zweig des Schalters nicht eingegeben wird.

Paul
quelle
+1 für die nette Erklärung der Deklaration und Initialisierung in Kompilierungszeit und Laufzeit.
Namalfernandolk
18

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

Deklarationen werden zur Kompilierungszeit verarbeitet und hängen nicht vom Ausführungsfluss Ihres Codes ab. Da valuees im lokalen Bereich des Switch-Blocks deklariert ist, kann es ab dem Zeitpunkt seiner Deklaration an einer beliebigen Stelle in diesem Block verwendet werden.

Müll
quelle
1
Warum wird diese Antwort positiv bewertet? es beantwortet die Frage nicht, im Gegensatz zu Paul oder
Skeets
7
Es tut. Also, +1, ein Penny, auch von meiner Seite.
Ravinder Reddy
3

Mit der Integration von JEP 325: Switch Expressions (Vorschau) in JDK-12-Builds für den frühen Zugriff. Es gibt bestimmte Änderungen, die aus Jons Antwort ersichtlich sind -

  1. Umfang lokaler Variablen - Die lokalen Variablen in den Switch-Fällen können jetzt lokal für den Fall selbst anstatt für den gesamten Switch-Block sein . Ein Beispiel (ähnlich wie Jon es auch syntaktisch versucht hatte) unter Berücksichtigung derDayEnum-Klasse zur weiteren Erklärung:

    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-Ausdrücke - Wenn die Absicht besteht, einer Variablen einen Wert zuzuweisen und ihn dann zu verwenden, können die Switch-Ausdrücke einmal verwendet werden. z.B

    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
quelle
0

Diese Erklärung könnte helfen.

    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
quelle
0

Java-Spezifikation:

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

Der Fall eines plötzlichen Abschlusses aufgrund eines Bruches mit einem Etikett wird durch die allgemeine Regel für beschriftete Anweisungen (§14.7) behandelt.

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

Beschriftete Aussagen:

LabeledStatement: Bezeichner: Anweisung

LabeledStatementNoShortIf: Bezeichner: StatementNoShortIf

Im Gegensatz zu C und C ++ hat die Programmiersprache Java keine goto-Anweisung. Bezeichner-Anweisungsbezeichnungen werden mit break-Anweisungen (§14.15) oder continue-Anweisungen (§14.16) verwendet, die an einer beliebigen Stelle in der beschrifteten Anweisung erscheinen.

Der Geltungsbereich einer Kennzeichnung einer gekennzeichneten Anweisung ist die unmittelbar enthaltene Anweisung.

Mit anderen Worten, Fall 1, Fall 2 sind Bezeichnungen innerhalb der switch-Anweisung. break and continue-Anweisungen können auf Beschriftungen angewendet werden.

Da Beschriftungen den Gültigkeitsbereich der Anweisung gemeinsam nutzen, teilen sich alle in Beschriftungen definierten Variablen den Gültigkeitsbereich der switch-Anweisung.

Pavel Molchanov
quelle