Deklarowanie i inicjowanie zmiennych w przełącznikach Java

99

Mam szalone pytanie dotyczące przełączników Java.

int key = 2;

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

Scenariusz 1 - gdy keyjest dwa, pomyślnie drukuje wartość jako 2.
Scenariusz 2 - Kiedy mam zamiar skomentować value = 2w case 2:nim, wrzeszczy, mówiąc, że wartość zmiennej lokalnej mogła nie zostać zainicjowana .

Pytania:

Scenariusz 1: Jeśli przepływ wykonania nie przejdzie do case 1:(kiedy key = 2), to skąd będzie wiedzieć, jaki typ zmiennej wartości int?

Scenariusz 2: Jeśli kompilator zna typ zmiennej wartości jako int, to musi mieć dostęp do int value = 1;wyrażenia w case 1:. (Deklaracja i inicjalizacja). Więc dlaczego to robi sqawrk Kiedy mam zamiar wypowiedzieć value = 2się case 2:, mówiąc lokalną wartość zmiennej nie została zainicjowana .

namalfernandolk
źródło
13
To nie jest szalone pytanie, to bardzo dobre pytanie.
biziclop
Możliwy duplikat zakresu zmiennej w przypadku przełącznika
Philippe Carriere
@PhilippeCarriere Właściwie myślę, że powinno być odwrotnie - tutaj odpowiedź jest lepsza (nawet jeśli post jest nowszy), ponieważ istnieje bezpośrednie odniesienie do JLS i dobrze podsumowuje kwestię omówioną w różnych odpowiedziach w tym poście. Zobacz także .
Tunaki
@Tunaki Opis duplikatu zaczyna się od „To pytanie zadawano już wcześniej”. Czytam, że późniejszy powinien być oznaczony jako duplikat wcześniejszego. Ale zgadzam się, że ten ma fajne elementy. Może należy je jakoś połączyć?
Philippe Carriere
Również wiele pytań dotyczących SO jest oznaczonych jako duplikaty mojego pierwotnego pytania, więc jeśli zdecydujesz, że lepiej oznaczyć to pytanie jako nowy oryginał, popraw wszystkie linki tak, aby odnosiły się do tego zamiast mojego.
Philippe Carriere

Odpowiedzi:

114

Instrukcje przełączania są zasadniczo dziwne pod względem zakresu. Z sekcji 6.3 JLS :

Zakres deklaracji zmiennej lokalnej w bloku (punkt 14.4) to reszta bloku, w którym deklaracja się pojawia, zaczynając od własnego inicjatora i włączając wszelkie dalsze deklaratory po prawej stronie w instrukcji deklaracji lokalnej zmiennej.

W twoim przypadku, case 2to w tym samym bloku , jak case 1i pojawia się po to, mimo że case 1nigdy nie będzie wykonać ... więc zmienna lokalna jest dostępny w zakresie i na pisanie mimo was logicznie nigdy „wykonanie” oświadczenie. (Deklaracja nie jest tak naprawdę „wykonywalna”, chociaż inicjalizacja jest.)

Jeśli zakomentujesz value = 2;przypisanie, kompilator nadal wie, do której zmiennej się odnosisz, ale nie przejdziesz przez żadną ścieżkę wykonania, która przypisuje mu wartość, dlatego otrzymujesz błąd, tak jak przy próbie przeczytaj dowolną inną zmienną lokalną nie przypisaną na pewno.

Zdecydowanie zalecamy nie używać zmiennych lokalnych zadeklarowanych w innych przypadkach - prowadzi to do bardzo mylące kod, jak widzieliście. Kiedy wprowadzam zmienne lokalne w instrukcjach switch (co staram się robić rzadko - przypadki powinny być bardzo krótkie, najlepiej), zazwyczaj wolę wprowadzić nowy zakres:

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

Uważam, że to jest jaśniejsze.

Jon Skeet
źródło
11
+1 dla "Deklaracja nie jest tak naprawdę" wykonywalna ", chociaż inicjalizacja jest.". Dziękuję również za porady Skeet.
namalfernandolk
1
Po zintegrowaniu JEP-325 obraz zakresu zmiennych lokalnych zmienił się i można używać tej samej nazwy w różnych przypadkach zamiast bloków przełączników. Chociaż opiera się również na podobnym kodowaniu blokowym. Ponadto wartość przypisana do zmiennej na przypadek przełącznika byłaby bardzo wygodna w przypadku wyrażeń przełącznika.
Naman
Punkty za dodanie nowego zakresu z nawiasami klamrowymi. Nawet nie wiedziałem, że możesz to zrobić.
Pływający Sunfish
21

Zmienna została zadeklarowana (jako liczba całkowita), ale nie została zainicjowana (przypisano jej wartość początkową). Pomyśl o linii:

int value = 1;

Tak jak:

int value;
value = 1;

int valueCzęść informuje kompilator w czasie kompilacji, że masz zmienną wartość, która jest int. value = 1Część inicjuje go, ale zdarza się, że w czasie wykonywania, a nie stanie, jeśli w ogóle, że oddział przełącznika nie jest wpisany.

Paweł
źródło
+1 za ładne wyjaśnienie deklaracji i inicjalizacji w czasie kompilacji i czasie wykonywania.
namalfernandolk
18

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

Deklaracje są przetwarzane w czasie kompilacji i nie zależą od przepływu wykonywania kodu. Ponieważ valuejest zadeklarowany w lokalnym zakresie bloku przełącznika, można go używać w dowolnym miejscu w tym bloku od momentu jego deklaracji.

Śmieci
źródło
1
dlaczego ta odpowiedź jest poparta? nie odpowiada na pytanie, w przeciwieństwie do odpowiedzi Paula czy
Skeeta
7
To robi. Więc +1 grosz, także z mojej strony.
Ravinder Reddy
3

Dzięki integracji JEP 325: Switch Expressions (Preview) w wersjach wczesnego dostępu JDK-12. Istnieją pewne zmiany, które można zobaczyć w odpowiedzi Jona -

  1. Zakres zmiennej lokalnej - zmienne lokalne w przypadkach przełączników mogą być teraz lokalne dla samej sprawy zamiast całego bloku przełącznika . Przykład (podobny do tego, co Jon również próbował syntaktycznie) rozważającyDayklasę wyliczenia w celu dalszego wyjaśnienia:

    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. Przełącz wyrażenia - jeśli celem jest przypisanie wartości do zmiennej, a następnie wykorzystanie jej, raz można użyć wyrażeń przełączających. na przykład

    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
źródło
0

To wyjaśnienie może pomóc.

    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
źródło
0

Specyfikacja Java:

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

W przypadku nagłego zakończenia z powodu zerwania etykiety stosuje się ogólną zasadę dotyczącą stwierdzeń opatrzonych etykietą (§14.7).

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

Oznakowane oświadczenia:

LabeledStatement: Identifier: Statement

LabeledStatementNoShortIf: Identifier: StatementNoShortIf

W przeciwieństwie do C i C ++, język programowania Java nie ma instrukcji goto; Etykiety instrukcji identyfikacyjnych są używane z instrukcjami break (§14.15) lub continue (§14.16) pojawiającymi się w dowolnym miejscu w oznaczonej instrukcji.

Zakres etykiety oznaczonej instrukcji to bezpośrednio zawarta Instrukcja.

Innymi słowy, przypadek 1, przypadek 2 to etykiety w instrukcji switch. Instrukcje break i continue można zastosować do etykiet.

Ponieważ etykiety współużytkują zakres instrukcji, wszystkie zmienne zdefiniowane w etykietach współużytkują zakres instrukcji switch.

Pavel Molchanov
źródło