我有一个问题,使用开关情况的instanceof对象:

例如:我的问题可以在Java中重现:

if(this instanceof A)
    doA();
else if(this instanceof B)
    doB();
else if(this instanceof C)
    doC():

如何使用switch…case实现它?


当前回答

从Java 17开始,您可以对开关表达式(预览特性)JEP-406使用模式匹配。

public void doAction(Object o) {
    return switch (o) {
        case A a -> doA(a);
        case B b -> doB(b);
        case C c -> doC(c);
        default -> log.warn("Unrecognized type of {}", o);
    };
}

防护模式也可用:

public void doAction(Object o) {
    return switch (o) {
        case String s && !s.isBlank() -> handle(s);
    };
}

你需要启用预览功能来使用它:java——enable-preview

其他回答

不幸的是,这是不可能开箱即用的,因为开关case语句需要一个常量表达式。要克服这个问题,一种方法是将枚举值与类名一起使用。

public enum MyEnum {
   A(A.class.getName()), 
   B(B.class.getName()),
   C(C.class.getName());

private String refClassname;
private static final Map<String, MyEnum> ENUM_MAP;

MyEnum (String refClassname) {
    this.refClassname = refClassname;
}

static {
    Map<String, MyEnum> map = new ConcurrentHashMap<String, MyEnum>();
    for (MyEnum instance : MyEnum.values()) {
        map.put(instance.refClassname, instance);
    }
    ENUM_MAP = Collections.unmodifiableMap(map);
}

public static MyEnum get(String name) {
    return ENUM_MAP.get(name);
 }
}

这样就可以像这样使用switch语句

MyEnum type = MyEnum.get(clazz.getName());
switch (type) {
case A:
    ... // it's A class
case B:
    ... // it's B class
case C:
    ... // it's C class
}

Java现在允许你以op的方式切换。他们称之为模式匹配切换。它在Java 17发布。JEP中给出的例子是

String formatted;
switch (obj) {
    case Integer i: formatted = String.format("int %d", i); break;
    case Byte b:    formatted = String.format("byte %d", b); break;
    case Long l:    formatted = String.format("long %d", l); break;
    case Double d:  formatted = String.format("double %f", d); break;
    case String s:  formatted = String.format("String %s", s); break
    default:        formatted = obj.toString();
}  

或者使用他们的lambda语法并返回一个值

String formatted = 
    switch (obj) {
        case Integer i -> String.format("int %d", i)
        case Byte b    -> String.format("byte %d", b);
        case Long l    -> String.format("long %d", l); 
        case Double d  -> String.format("double %f", d); 
        case String s  -> String.format("String %s", s); 
        default        -> obj.toString();
    };

不管怎样,他们一直在用开关做很酷的东西。

正如前面的回答所讨论的,传统的面向对象方法是使用多态性而不是开关。对于这种技巧,甚至有一个经过良好记录的重构模式:用多态性替换Conditional。每当我使用这种方法时,我也喜欢实现一个Null对象来提供默认行为。

从Java 8开始,我们可以使用lambda和泛型来提供函数式程序员非常熟悉的东西:模式匹配。它不是语言的核心特性,但是VAVR库(以前是Javaslang库)提供了一个实现。文档中的例子:

Match.ofType(Number.class)
    .caze((Integer i) -> i)
    .caze((String s) -> new BigDecimal(s))
    .orElse(() -> -1)
    .apply(1.0d); // result: -1

它不是Java世界中最自然的范例,所以要谨慎使用。虽然泛型方法将使您不必对匹配的值进行类型转换,但我们缺少一种分解匹配对象的标准方法,例如Scala的case类。

这是子类型多态有助于实现的典型场景。执行以下步骤

interface I {
  void do();
}

class A implements I { void do() { doA() } ... }
class B implements I { void do() { doB() } ... }
class C implements I { void do() { doC() } ... }

然后您可以简单地在此上调用do()。

如果您不能自由地更改A、B和C,则可以应用访问者模式来实现相同的目的。

在最近发布的Java 19中,这非常简单和有用: 创建接口,并具有名为makesomenoise()的方法,它接受动物参数。

void makeSomeNoise (Animal animal) {
switch (animal) {
case Dog dog → dog.bark();
case Cat catcat.meow();
default throw new RuntimeException ("WTH is it???");
}
}