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

在上面的代码中,我不能在switch case语句中使用null。我该怎么做呢?我不能用默认,因为我想做别的。


当前回答

考虑到:

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

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

然而,当我试图使用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 @交换机(personType){。我开始实现的一个变通方法(即“解决方案”)是添加一个UNKNOWN(-1)类型。

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

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

其他回答

这在Java 18之前是不可能用switch语句实现的。在切换之前必须检查是否为空。但是现在,有了模式匹配,这些都成为过去式了。看看JEP 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");   
  }
}

更多关于开关(包括空变量的例子)在Oracle Docs - switch

基于@tetsuo的答案,java 8:

Integer i = ...

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

带开关的模式匹配

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

Switches and null Traditionally, switch statements and expressions throw NullPointerException if the selector expression evaluates to null, so testing for null must be done outside of the switch. [...] This was reasonable when switch supported only a few reference types. However, if switch allows a selector expression of any type, and case labels can have type patterns, then the standalone null test feels like an arbitrary distinction, and invites needless boilerplate and opportunity for error. It would be better to integrate the null test into the switch by allowing a new null case label.

参见JEP 433:模式匹配开关(第四预览)

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

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

但如果省略大小写null:-part,开关仍然会抛出NullPointerException

考虑到:

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

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

然而,当我试图使用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 @交换机(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库的依赖…