在Java中使用switch语句声明和初始化变量

111

我有一个有关Java中switch语句的疯狂问题。

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。
场景2 - 当我要在 case 2: 中注释掉 value = 2 时,它会发出警告,提示 本地变量值可能未初始化

问题:

场景1:如果执行流不进入 case 1:(当 key = 2 时),那么它怎么知道值变量的类型是 int

场景2:如果编译器知道值变量的类型是 int,那么它一定已经访问了 case 1: 中的表达式 int value = 1; (声明和初始化)。那么为什么当我试图在 case 2: 中注释掉 value = 2 时,它会发出警告,提示 本地变量值可能未初始化


16
这不是一个疯狂的问题,而是一个非常好的问题。 - biziclop
可能是switch case语句中变量的作用域的重复问题。 - Philippe Carriere
@PhilippeCarriere 实际上,我认为应该反过来 - 这里的答案更好(即使帖子更新)因为它直接引用了JLS,并且很好地总结了在该帖子中不同答案所涵盖的问题。另请参阅 - Tunaki
@Tunaki,重复问题的描述以“此问题已被提出”开头。我理解为后一个应该标记为前一个的重复。但是我同意这个问题具有良好的元素。也许它们应该以某种方式合并? - Philippe Carriere
还有很多在SO上的问题被标记为我的原始问题的重复,因此如果您决定将此问题标记为新的原始问题,请修复所有链接以引用该问题而不是我的问题。 - Philippe Carriere
6个回答

122

基本上,从作用域的角度来看,switch语句很奇怪。引用自JLS的第6.3节:

块(§14.4)中局部变量声明的作用域是出现该声明的块的其余部分,以及在局部变量声明语句中向右进一步声明符号的任何部分,包括其自己的初始化器。

在你的情况下,case 2case 1在同一段内,并且出现在它之后,尽管case 1永远不会执行...所以即使你逻辑上从未“执行”过声明,该局部变量仍然处于作用域内并可供写入。(声明本身并非“可执行”的,但初始化是可以执行的。)

如果您注释掉value = 2;赋值,编译器仍然知道您要引用哪个变量,但您没有经过任何赋值的执行路径,因此会像读取其他未明确赋值的局部变量时一样报错。

我强烈建议您不要使用在其他case中声明的局部变量-这会导致代码非常混乱,正如您所看到的一样。当我在switch语句中引入局部变量时(我尽量少这样做- case应该非常短,最好),我通常更喜欢引入一个新的作用域:

case 1: {
    int value = 1;
    ...
    break;
}
case 2: {
    int value = 2;
    ...
    break;
}
我相信这更清晰明了。

11
感谢建议,对于“声明并不是真正的'可执行',尽管初始化是。”给予+1的支持。感谢Skeet的建议。 - ironwood
1
随着JEP-325的集成,本地变量范围的图像已经改变,并且可以在不同情况下使用相同的名称,而不是switch块。虽然它也依赖于类似的块编码。此外,使用switch表达式分配给变量的值将更加方便。 - Naman
1
添加新的作用域时加上大括号会得到额外的分数。我甚至不知道你可以这样做。 - Floating Sunfish

22

该变量已被声明(作为int类型),但未初始化(赋初值)。想象一下这行代码:

int value = 1;

如下:

int value;
value = 1;

int value 部分告诉编译器在编译时你有一个名为value的变量,它是一个整数。 value = 1 部分对其进行初始化,但这发生在运行时,并且如果该开关语句中没有进入该分支,则根本不会发生。


对于编译时和运行时的声明和初始化,你的解释非常好,点个赞。 - ironwood

18

1
为什么这个答案被投票赞成?它并没有回答问题,不像 Paul 或 Skeet 的答案... - Dhruv Gairola
7
没错,我也给你加一分,一分钱。 - Ravinder Reddy

7

随着JDK-12早期版本的集成JEP 325: Switch Expressions (Preview),可以从Jon's answer中看到一些变化 -

  1. Local Variable Scope - The local variables in the switch cases can now be local to the case itself instead of the entire switch block. An example (similar to what Jon had attempted syntactically as well) considering the Day enum class for further explanation :

    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 - If the intent is to assign a value to a variable and then make use of it, once can make use of the switch expressions. e.g.

    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

标记语句:

标记语句: 标识符 : 语句

无短路if的标记语句: 标识符 : 无短路if语句

与C和C++不同,Java编程语言没有goto语句;标识符语句标签可用于出现在标记语句内任何位置的break(§14.15)或continue(§14.16)语句。

标记语句的标签范围是立即包含的语句。

换句话说,case 1,case 2是switch语句内的标签。可以对标签应用break和continue语句。

由于标签共享语句的范围,所有在标签内定义的变量都共享switch语句的范围。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接