2

有人能告诉我 Java 17 接受 final 表达式作为 switch-case-constructs 中的 case 表达式但不接受 final 表达式作为参数传递的好处吗?

    void test(int distinction, final int foo) {
        final var bar = 2;
        
        switch (distinction) {
        case foo -> doSomething(); // does not compile -> case expressions must be constant expressions
        case bar -> doSomething(); // does compile
        case 3 -> doOtherThings(); // does compile
        }
    }

为什么编译器不接受案例 1,尽管 foo 和 bar 一样是最终变量?

在我看来,案例 3 的可读性比案例 2 好得多。所以我看不到新语言结构的好处。

4

2 回答 2

9

案例标签必须是编译时常量。最终参数不是编译时常量;它可能不会随着方法的给定调用而变化,但它可以随着方法的调用而变化。(最终实例字段和没有初始化器的静态最终字段也不是编译时常量。)

于 2021-12-11T19:09:29.147 回答
2

你的陈述“所以我没有看到新语言结构的好处”包含了一个错误的假设,即涉及到一个新的语言结构。

自 Java 1.0 以来,编译时常量的定义没有改变。

常量变量final原始类型或String使用常量表达式(第 15.29 节)初始化的类型的变量。

因此,与流行的顽固神话相反,编译时常量不一定是,static也根本不需要是一个字段。用常量表达式初始化的final局部变量是编译时常量。相反,从不具有初始值设定项的参数永远不会是编译时常量。

此外,当switch标签是整数类型时,标签必须是编译时常量的规则永远不会改变。这更难识别,因为添加了对其他 case 标签的支持,Java 5 引入了切换enum类型的可能性,Java 7 增加了对切换String值的支持,并且新的模式匹配将允许切换类型。在enumor 类型标签中,case 标签不是编译时常量,而是不变的符号名称,因此在切换enumor 类型时根本不能使用变量名称。

因此,以下程序适用于每个 Java 版本:

class SwitchTest {
    public static void main(String[] args) {
        final int one = 1, two = 2;
        switch(args.length) {
          case one: System.out.println("one"); break;
          case two: System.out.println("two"); break;
          case one + two: System.out.println("three"); break;
        }
    }
}

这里不需要实际的用例。拥有简单而一致的规则比拥有试图排除看似无用的组合的复杂规则要好。

作为附录,从Java 5开始,引入注解,以下是合法代码

class Test {
    @interface Example { String value(); }
    public static void main(String[] args) {
        final String str = "test";
        @Example(str) class Local {}
    }
}

这证明了规则的一致性。注释要求值是编译时常量,因此如果变量是编译时常量,则可以在注释中使用局部变量。

于 2021-12-13T13:23:29.697 回答