如何在交换机中使用空值

Integer i = ...
    

switch (i) {
case null:
doSomething0();
break;
}

在上面的代码中,我不能在 switch case 语句中使用 null。我该怎么做才能有所不同呢?我不能使用 default,因为那样我就想做其他事情。

278932 次浏览
< p >你不能。你可以在switch中使用基本类型(int, char, short, byte)和String(仅在java 7中使用字符串)。基本类型不能为空 在开关前检查i是否处于单独的状态

如果i是nullswitch(i)将抛出NullPointerException,因为它将尝试将Integer拆箱为int。所以case null,它恰好是非法的,无论如何都不会被到达。

你需要在switch语句之前检查i是否为空。

你必须做出一个

if (i == null) {
doSomething0();
} else {
switch (i) {
}
}

在Java 18之前,在Java中使用switch语句是不可能实现的。你必须在switch之前检查null。但是现在,有了模式匹配,这些都成为过去式了。看一下中420:

模式匹配和空

传统上,switch语句和表达式会抛出 如果选择器表达式的结果为null,则为NullPointerException null测试必须在开关外部完成:

static void testFooBar(String s) {
if (s == null) {
System.out.println("oops!");
return;
}
switch (s) {
case "Foo", "Bar" -> System.out.println("Great");
default           -> System.out.println("Ok");
}
}
当switch只支持少数参考类型时,这是合理的。 然而,if switch允许任何类型的选择器表达式和大小写 标签可以有类型模式,这样独立的空测试才有感觉 就像一个武断的区分,并引起不必要的样板文件和 犯错的机会。最好是集成空测试 进入开关:

static void testFooBar(String s) {
switch (s) {
case null         -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default           -> System.out.println("Ok");
}
}

有关switch的更多信息(包括空变量的示例),请参见Oracle Docs - Switch

Java文档明确指出:

禁止使用null作为开关标签可以防止编写永远不能执行的代码。如果开关表达式是引用类型,例如盒装基元类型或enum,如果表达式在运行时求值为空,则会发生运行时错误。

在switch语句执行之前,必须验证是否为空。

if (i == null)

看到Switch语句

case null: // will never be executed, therefore disallowed.
switch ((i != null) ? i : DEFAULT_VALUE) {
//...
}

考虑到:

public enum PersonType {
COOL_GUY(1),
JERK(2);


private final int typeId;
private PersonType(int typeId) {
this.typeId = typeId;
}


public final int getTypeId() {
return typeId;
}


public static PersonType findByTypeId(int typeId) {
for (PersonType type : values()) {
if (type.typeId == typeId) {
return type;
}
}
return null;
}
}

对我来说,这通常与数据库中的查找表(仅针对很少更新的表)保持一致。

然而,当我尝试在switch语句中使用findByTypeId时(很可能来自用户输入)…

int userInput = 3;
PersonType personType = PersonType.findByTypeId(userInput);
switch(personType) {
case COOL_GUY:
// Do things only a cool guy would do.
break;
case JERK:
// Push back. Don't enable him.
break;
default:
// I don't know or care what to do with this mess.
}

...正如其他人所述,这将导致NPE @ switch(personType) {。我开始实现的一个变通方法(即“解决方案”)是添加UNKNOWN(-1)类型。

public enum PersonType {
UNKNOWN(-1),
COOL_GUY(1),
JERK(2);
...
public static PersonType findByTypeId(int id) {
...
return UNKNOWN;
}
}

现在,你不必在计数的地方做空检查,你可以选择是否处理UNKNOWN类型。(注意:-1在业务场景中不太可能是一个标识符,但显然要选择对您的用例有意义的标识符)。

一些库尝试提供内置java switch语句的替代方法。Vavr是其中之一,它们将其泛化为模式匹配。

下面是一个来自他们的文档的例子:

String s = Match(i).of(
Case($(1), "one"),
Case($(2), "two"),
Case($(), "?")
);

您可以使用任何谓词,但它们提供了许多开箱即用的谓词,并且$(null)是完全合法的。我发现这是一个比替代方案更优雅的解决方案,但这需要java8和对vavr库的依赖…

switch (String.valueOf(value)){
case "null":
default:
}
你也可以使用String.valueOf((Object) nullableString) 像< / p >
switch (String.valueOf((Object) nullableString)) {
case "someCase"
//...
break;
...
case "null": // or default:
//...
break;
}

参见有趣的SO Q/A: 为什么String.valueOf(null)抛出NullPointerException

想想SWITCH是如何工作的,

  • 对于原语,我们知道NPE自动装箱可能会失败
  • 但对于字符串枚举,它可能正在调用equals方法,这显然需要一个LHS值来调用equals。 因此,给定null对象上不能调用任何方法,开关不能处理null

基于@tetsuo的答案,java 8:

Integer i = ...


switch (Optional.ofNullable(i).orElse(DEFAULT_VALUE)) {
case DEFAULT_VALUE:
doDefault();
break;
}

我今天才知道,你不必放另一层缩进/花括号,只是因为if检查为空。你可以做以下任何一种:

if (i == null) {
// ...
} else switch (i) {
case 1:
// ...
break;
default:
// ...
}

if (i != null) switch (i) {
case 1:
// ...
break;
default:
// ...
} else {
// ...
}

使用switch进行模式匹配

switch语句的null行为将随着模式匹配的添加而改变(在JDK 17/18/19/20中有预览状态)

开关和空

传统上,switch语句和表达式会抛出 如果选择器表达式的值为null,则为NullPointerException null的测试必须在开关外部进行

[…]

当switch只支持少数参考类型时,这是合理的。 然而,if switch允许任何类型的选择器表达式和大小写 标签可以有类型模式,这样独立的空测试才有感觉 就像一个武断的区分,并引起不必要的样板文件和 犯错的机会。最好是集成空测试 通过允许一个新的空大小写标签.

. 0进入开关

看到JEP 433: Pattern Matching for switch (Fourth Preview)

这基本上意味着你可以只是简单地写

switch (null) {
case null: ...
}

但如果省略__abc0部分,开关仍将抛出NullPointerException