Объявление и инициализация переменных в переключателях Java


99

У меня сумасшедший вопрос о переключателях Java.

int key = 2;

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

Сценарий 1 - Когда keyэто два успешно напечатать значение , как 2.
Сценарий 2 - Когда я буду комментировать value = 2в case 2:это коды ответчика говоря , что значение локальной переменной не может быть инициализирован .

Вопросы:

Сценарий 1: Если поток выполнения не переходит case 1:(когда key = 2), то как он узнает тип переменной значения как int?

Сценарий 2: Если компилятор знает тип переменной значения как int, то он должен иметь доступ к int value = 1;выражению в case 1:. (Объявление и инициализация). Тогда почему это sqawrk Когда я буду комментировать value = 2в case 2:, говоря , что Значение локальной переменной не может быть инициализирован .


13
Это не сумасшедший вопрос, это очень хороший вопрос.
biziclop


@PhilippeCarriere На самом деле, я думаю, что это должно быть наоборот - ответ здесь лучше (даже если сообщение более новое), поскольку есть прямая ссылка на JLS и хорошо резюмирует проблему, затронутую в разных ответах в этом сообщении. См. Также .
Tunaki

@Tunaki Описание дубликата начинается с «Этот вопрос уже задавался ранее». Я читаю это, поскольку более поздний должен быть отмечен как дубликат более раннего. Но я согласен, что у этого есть хорошие элементы. Может их как-то объединить?
Philippe Carriere

Также многие вопросы по SO помечены как дубликаты моего исходного вопроса, поэтому, если вы решите, что лучше пометить этот вопрос как новый оригинал, исправьте все ссылки, чтобы ссылаться на этот, а не на мой.
Philippe Carriere

Ответы:


114

Операторы switch в основном странны с точки зрения объема. Из раздела 6.3 JLS :

Объем объявления локальной переменной в блоке (§14.4) - это остальная часть блока, в котором появляется объявление, начиная со своего собственного инициализатора и включая любые дальнейшие деклараторы справа в операторе объявления локальной переменной.

В вашем случае case 2находится в том же блоке, что case 1и, и появляется после него, хотя case 1никогда не будет выполняться ... поэтому локальная переменная находится в области видимости и доступна для записи, несмотря на то, что вы логически никогда не «выполняете» объявление. (Объявление не является исполняемым, хотя инициализация есть.)

Если вы закомментируете value = 2;присвоение, компилятор по-прежнему знает, на какую переменную вы ссылаетесь, но вы не прошли через какой-либо путь выполнения, который присваивает ему значение, поэтому вы получаете сообщение об ошибке, как если бы вы пытались прочтите любую другую локальную переменную с неопределенным назначением.

Я настоятельно рекомендую вам не использовать локальные переменные, объявленные в других случаях - как вы видели, это приводит к очень запутанному коду. Когда я ввожу локальные переменные в операторы switch (что я стараюсь делать редко - в идеале случаи должны быть очень короткими), я обычно предпочитаю ввести новую область видимости:

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

Я считаю, что это яснее.


11
+1 за «Объявление на самом деле не« исполняемый », хотя инициализация есть.». И спасибо за советы Skeet.
namalfernandolk

1
С интегрированным JEP-325 картина области действия локальных переменных изменилась, и теперь можно использовать одно и то же имя во всех случаях вместо блоков переключения. Хотя он также полагается на аналогичное блочное кодирование. Кроме того, значение, присвоенное переменной для каждого варианта переключения, было бы очень удобно с выражениями переключения.
Наман

Очки за добавление новой области с фигурными скобками. Даже не знал, что ты можешь это сделать.
Floating Sunfish

21

Переменная была объявлена ​​(как int), но не инициализирована (ей присвоено начальное значение). Подумайте о строчке:

int value = 1;

Так как:

int value;
value = 1;

Эта int valueчасть сообщает компилятору во время компиляции, что у вас есть переменная с именем value, которая является int. value = 1Часть инициализирует его, но это происходит во время выполнения, и не бывает вовсе , если эта ветвь переключателя не введена.


+1 за красивое объяснение объявления и инициализации во время компиляции и выполнения.
namalfernandolk

18

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

Объявления обрабатываются во время компиляции и не зависят от потока выполнения вашего кода. Поскольку valueон объявлен в локальной области видимости блока переключателя, его можно использовать в любом месте этого блока с момента его объявления.


1
почему за этот ответ проголосовали? он не отвечает на вопрос, в отличие от ответа
Пола или Скита

7
Оно делает. Итак, +1, копейка, тоже с моей стороны.
Рэвиндер Редди

3

Благодаря интеграции JEP 325: Switch Expressions (Preview) в сборки раннего доступа JDK-12. Есть определенные изменения, которые можно увидеть из ответа Джона -

  1. Область действия локальной переменной - локальные переменные в вариантах переключения теперь могут быть локальными для самого случая, а не для всего блока переключения . Пример (похожий на то, что Джон пытался синтаксически) с учетомDayкласса enum для дальнейшего объяснения:

    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. Выражения переключения - если намерение состоит в том, чтобы присвоить значение переменной, а затем использовать его, один раз можно использовать выражения переключения. например

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

0

Это объяснение может помочь.

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

0

Спецификация Java:

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

Случай внезапного завершения из-за разрыва с меткой обрабатывается общим правилом для помеченных операторов (§14.7).

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

Помеченные заявления:

LabeledStatement: Идентификатор: Заявление

LabeledStatementNoShortIf: Идентификатор: StatementNoShortIf

В отличие от C и C ++, в языке программирования Java нет оператора goto; метки операторов идентификатора используются с операторами break (§14.15) или continue (§14.16), появляющимися в любом месте помеченного оператора.

Объем метки помеченного оператора - это непосредственно содержащийся оператор.

Другими словами, case 1, case 2 - это метки внутри оператора switch. К меткам можно применять операторы break и continue.

Поскольку метки разделяют область действия оператора, все переменные, определенные в метках, разделяют область действия оператора switch.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.