Java Switch开关表达式

Switch Expressions 是在Project Amber下的Java 14中发布的。

Switch Expressions实际上是两个可以独立使用也可以组合使用的增强功能:

  1. break没有和贯穿fall-throughs的箭头符号
  2. 用作switch带有返回值的表达式

让我们一一看一下变化(我使用的是JEP 361中的示例,稍作修改)。

初始点

在以下示例中,我们打印一周中给定日期的单词长度。对于最后两种情况,我编写的代码比必要的要复杂一些,以演示 switch 表达式的可能性。

switch (day) {
  case MONDAY:
  case FRIDAY:
  case SUNDAY:
    System.out.println(6);
    break;
  case TUESDAY:
    System.out.println(7);
    break;
  case THURSDAY:
  case SATURDAY:
    System.out.println((int) Math.pow(2, 3));
    break;
  case WEDNESDAY:
    int three = 1 + 2;
    System.out.println(three * three);
    break;
}

由于所谓的“失败”(fall-throughs),即如果前一个案例没有以 break 语句终止,则在下一个案例中继续执行,这种表示法令人困惑且容易出错。

因此,Sonar、Checkstyle 和 PMD 等静态代码分析 (SCA) 工具将此类构造标记为代码异味:

没有“break”的“switch”语句的静态代码分析 (SCA) 警告

使用箭头符号切换

以下示例显示使用箭头而不是冒号,从 Java 14 开始。以下规则适用:

  • 在箭头之前,您可以列出几个用逗号分隔的案例。
  • 箭头后面可以跟单个代码语句(第 2、3 和 4 行)或花括号中的代码块(第 5 到 8 行)。
  • break在这种表示法中省略了语句。

以下是switch箭头符号语句的示例代码:

switch (day) {
  case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
  case TUESDAY                -> System.out.println(7);
  case THURSDAY, SATURDAY     -> System.out.println((int) Math.pow(2, 3));
  case WEDNESDAY -> {
    int three = 1 + 2;
    System.out.println(three * three);
  }
}

像 IntelliJ 这样的现代 IDE 认识到了改进的潜力,并提供了自动转换为新格式的功能:

自动替换“switch”语句

Switch 作为带有返回值的表达式

我们经常使用switch将特定于案例的值分配给变量。在以下示例中,我们将其存储在numLetters变量中,而不是打印工作日的长度:

(最后两个案例再次被有意冗长地写出来,以展示 switch 表达式的可能性。)

int numLetters;
switch (day) {
  case MONDAY:
  case FRIDAY:
  case SUNDAY:
    numLetters = 6;
    break;
  case TUESDAY:
    numLetters = 7;
    break;
  case THURSDAY:
  case SATURDAY:
    numLetters = (int) Math.pow(2, 3);
    break;
  case WEDNESDAY:
    int three = 1 + 2;
    numLetters = three * three;
    break;
  default:
    throw new IllegalStateException("Unknown day: " + day);
}

之后要使用该变量,我们必须——即使我们已经涵盖了每个工作日——要么事先初始化变量,要么指定一个默认情况。否则,编译器将中止并显示错误消息“Variable ‘numLetters’ might not have been initialized”

从 Java 14 开始,我们可以将此语句转换为表达式。为此,我们使用 new 关键字返回每个值yield。然后我们将 switch 表达式的结果直接赋值给变量:

int numLetters = switch (day) {
  case MONDAY:
  case FRIDAY:
  case SUNDAY:
    yield 6;

  case TUESDAY:
    yield 7;

  case THURSDAY:
  case SATURDAY:
    yield (int) Math.pow(2, 3);

  case WEDNESDAY:
    int three = 1 + 2;
    yield three * three;

  default:
    throw new IllegalStateException("Unknown day: " + day);
};

yield是所谓的“上下文关键词”;switch因此,它仅在表达式的上下文中才有意义。如果你yield在源代码中使用了变量名——别担心;你仍然可以这样做。即使是这样的事情也是允许的:

int yield = 5; yield yield + yield;

箭头符号和switch表达式相结合

如果我们用箭头符号来写,刚才显示的 switch 表达式会变得更加优雅。我们可以写返回值

  • 直接在箭头后面(第 2 行和第 3 行),
  • 作为箭头后面的表达式或方法调用(第 4 行),
  • yield从代码块(第 7 行)返回它。
int numLetters = switch (day) {
  case MONDAY, FRIDAY, SUNDAY -> 6;
  case TUESDAY                -> 7;
  case THURSDAY, SATURDAY     -> (int) Math.pow(2, 3);
  case WEDNESDAY              -> {
    int three = 1 + 2;
    yield three * three;
  }
  default -> throw new IllegalStateException("Unknown day: " + day);
};

我们还可以让我们的 IDE 完成从传统的 switch 语句到带有箭头符号的 switch 表达式的完整重构:

用“switch”表达式自动替换“switch”语句

枚举的完整性分析

由于变量 day 是一个枚举,编译器可以识别出我们已经涵盖了所有情况。因此,我们可以省略这种default情况:

int numLetters = switch (day) {
  case MONDAY, FRIDAY, SUNDAY -> 6;
  case TUESDAY                -> 7;
  case THURSDAY, SATURDAY     -> (int) Math.pow(2, 3);
  case WEDNESDAY              -> {
    int three = 1 + 2;
    yield three * three;
  }
};

我们的 IDE 也很乐意为我们这样做:

自动删除“默认”分支

没有默认情况的表示法更短,有助于我们将来扩展枚举。如果我们扩展它——例如,通过NEWDAYa——编译器会告诉我们 switch 表达式现在是不完整的:

不完整的“switch”表达式

所以 switch 表达式也可以让我们的代码更加健壮。

概括

switch表达式是一个强大的工具。箭头符号和用作具有返回值的表达式允许比以前更简洁且不易出错的符号。

Switch 表达式最初是在Java 12中作为预览功能引入的。在Java 13break的第二个预览版中,最初用于返回值的关键字被替换为yield. 在JDK Enhancement Proposal 361中,Switch Expressions 作为Java 14的最终功能发布,没有任何进一步的更改。