是否可以在switch语句中使用instanceof操作符?

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

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

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

如何使用switch...case实现它?

350457 次浏览

你不能只对byte, short, char, int, String和枚举类型(以及原语的对象版本,这也取决于你的java版本,在java 7中字符串可以switched on)进行切换。

你不能。switch语句只能包含case语句,这些语句是编译时常量,并计算为整数(最多Java 6和Java 7中的字符串)。

在函数式编程中,您要寻找的是所谓的“模式匹配”。

另见避免在Java中使用instanceof

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

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() } ... }

然后你可以在this上调用do()

如果你不能自由地改变ABC,你可以应用访问者模式来实现同样的目的。

不,这是不可能的。然而,你可能想要做的是考虑多态性作为处理这类问题的一种方式。

像这样使用switch语句不是面向对象的方式。相反,你应该使用多态性的力量。简单的写

this.do()

之前已经建立了一个基类:

abstract class Base {
abstract void do();
...
}

它是ABC的基类:

class A extends Base {
void do() { this.doA() }
}


class B extends Base {
void do() { this.doB() }
}


class C extends Base {
void do() { this.doC() }
}

如果你绝对不能编码到接口,那么你可以使用枚举作为中介:

public A() {


CLAZZ z = CLAZZ.valueOf(this.getClass().getSimpleName());
switch (z) {
case A:
doA();
break;
case B:
doB();
break;
case C:
doC();
break;
}
}




enum CLAZZ {
A,B,C;


}

以防万一有人读到

java中的最佳解决方案是:

public enum Action {
a{
void doAction(...){
// some code
}


},
b{
void doAction(...){
// some code
}


},
c{
void doAction(...){
// some code
}


};


abstract void doAction (...);
}

这种模式的最大好处是:

  1. 你只是这样做(没有开关):

    void someFunction ( Action action ) {
    action.doAction(...);
    }
    
  2. In case if you add new Action called "d" you MUST imlement doAction(...) method

NOTE: This pattern is described in Joshua's Bloch "Effective Java (2nd Edition)"

这个怎么样?

switch (this.name)
{
case "A":
doA();
break;
case "B":
doB();
break;
case "C":
doC();
break;
default:
console.log('Undefined instance');
}

我认为使用switch语句是有原因的。如果你使用xText生成的代码。或者另一种EMF生成的类。

instance.getClass().getName();

返回类实现名称的字符串。即: org.eclipse.emf.ecore.util.EcoreUtil < / p >

instance.getClass().getSimpleName();

返回简单表示,即: EcoreUtil < / p >

如果您可以操作公共接口,您可以在枚举中添加,并让每个类返回唯一的值。您不需要instanceof或visitor模式。

对我来说,逻辑需要写在switch语句中,而不是对象本身。这就是我的解决方案:

ClassA, ClassB, and ClassC implement CommonClass

接口:

public interface CommonClass {
MyEnum getEnumType();
}

枚举:

public enum MyEnum {
ClassA(0), ClassB(1), ClassC(2);


private int value;


private MyEnum(final int value) {
this.value = value;
}


public int getValue() {
return value;
}

Impl:

...
switch(obj.getEnumType())
{
case MyEnum.ClassA:
ClassA classA = (ClassA) obj;
break;


case MyEnum.ClassB:
ClassB classB = (ClassB) obj;
break;


case MyEnum.ClassC:
ClassC classC = (ClassC) obj;
break;
}
...

如果你使用的是java 7,你可以为枚举设置字符串值,并且switch case块仍然可以工作。

我知道这有点晚了,但对于未来的读者来说……

注意上面的方法,它们仅基于一个BC…类的的名字。:

除非你能保证一个BC…(所有基地的子类或实现者)都是最后,然后是一个BC…不会被处理。

尽管如果,elseif, elseif ..方法对于大量子类/实现者较慢,但它更准确。

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

从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类

创建一个Map,其中键为Class<?>,值为表达式(lambda或类似)。考虑:

Map<Class,Runnable> doByClass = new HashMap<>();
doByClass.put(Foo.class, () -> doAClosure(this));
doByClass.put(Bar.class, this::doBMethod);
doByClass.put(Baz.class, new MyCRunnable());


// of course, refactor this to only initialize once


doByClass.get(getClass()).run();

如果你需要检查异常,那么就实现一个FunctionalInterface来抛出Exception并使用它来代替Runnable


下面是一个真实的单词前后对比,展示了这种方法如何简化代码。

重构映射之前的代码:

private Object unmarshall(
final Property<?> property, final Object configValue ) {
final Object result;
final String value = configValue.toString();


if( property instanceof SimpleDoubleProperty ) {
result = Double.parseDouble( value );
}
else if( property instanceof SimpleFloatProperty ) {
result = Float.parseFloat( value );
}
else if( property instanceof SimpleBooleanProperty ) {
result = Boolean.parseBoolean( value );
}
else if( property instanceof SimpleFileProperty ) {
result = new File( value );
}
else {
result = value;
}


return result;
}

重构为映射后的代码:

private final Map<Class<?>, Function<String, Object>> UNMARSHALL =
Map.of(
SimpleBooleanProperty.class, Boolean::parseBoolean,
SimpleDoubleProperty.class, Double::parseDouble,
SimpleFloatProperty.class, Float::parseFloat,
SimpleFileProperty.class, File::new
);


private Object unmarshall(
final Property<?> property, final Object configValue ) {
return UNMARSHALL
.getOrDefault( property.getClass(), ( v ) -> v )
.apply( configValue.toString() );
}

这避免了重复,消除了几乎所有的分支语句,并简化了维护。

如果你需要通过“this”对象的类类型来“切换”,这个答案是最好的https://stackoverflow.com/a/5579385/2078368

但如果你需要将“switch”应用于任何其他变量。我建议另一种解决办法。定义以下接口:

public interface ClassTypeInterface {
public String getType();
}

在你想要“切换”的每个类中实现这个接口。例子:

public class A extends Something implements ClassTypeInterface {


public final static String TYPE = "A";


@Override
public String getType() {
return TYPE;
}
}

之后,你可以用以下方式使用它:

switch (var.getType()) {
case A.TYPE: {
break;
}
case B.TYPE: {
break;
}
...
}

你唯一需要关心的是——在实现ClassTypeInterface的所有类中保持“类型”的唯一性。这不是一个大问题,因为在任何交集的情况下,您都会收到“switch-case”语句的编译时错误。

还有一种更简单的方法来模拟使用instanceof的开关结构,你可以通过在方法中创建一个代码块并用标签命名它来实现。然后使用if结构来模拟case语句。如果情况为真,则使用断点LABEL_NAME来退出临时的开关结构。

        DEFINE_TYPE:
{
if (a instanceof x){
//do something
break DEFINE_TYPE;
}
if (a instanceof y){
//do something
break DEFINE_TYPE;
}
if (a instanceof z){
// do something
break DEFINE_TYPE;
}
}

我个人比较喜欢下面的Java 1.8代码:

    mySwitch("YY")
.myCase("AA", (o) -> {
System.out.println(o+"aa");
})
.myCase("BB", (o) -> {
System.out.println(o+"bb");
})
.myCase("YY", (o) -> {
System.out.println(o+"yy");
})
.myCase("ZZ", (o) -> {
System.out.println(o+"zz");
});

将输出:

YYyy

示例代码使用字符串,但您可以使用任何对象类型,包括Class。例如.myCase(this.getClass(), (o) -> ...

需要以下片段:

public Case mySwitch(Object reference) {
return new Case(reference);
}


public class Case {


private Object reference;


public Case(Object reference) {
this.reference = reference;
}


public Case myCase(Object b, OnMatchDo task) {
if (reference.equals(b)) {
task.task(reference);
}
return this;
}
}


public interface OnMatchDo {
public void task(Object o);
}

java 7 +

public <T> T process(Object model) {
switch (model.getClass().getSimpleName()) {
case "Trade":
return processTrade((Trade) model);
case "InsuranceTransaction":
return processInsuranceTransaction((InsuranceTransaction) model);
case "CashTransaction":
return processCashTransaction((CashTransaction) model);
case "CardTransaction":
return processCardTransaction((CardTransaction) model);
case "TransferTransaction":
return processTransferTransaction((TransferTransaction) model);
case "ClientAccount":
return processAccount((ClientAccount) model);
...
default:
throw new IllegalArgumentException(model.getClass().getSimpleName());
}
}

你可以通过在getSimpleName中引入常量并使用完整的类名来省略字符串操作,这样可以更快:

public static final TRADE = Trade.class.getName();
...
switch (model.getClass().getName()) {
case TRADE:

创建一个带有类名的枚举

public enum ClassNameEnum {
A, B, C
}

找到对象的类名。 在枚举上写一个开关的case

private void switchByClassType(Object obj) {


ClassNameEnum className = ClassNameEnum.valueOf(obj.getClass().getSimpleName());


switch (className) {
case A:
doA();
break;
case B:
doB();
break;
case C:
doC();
break;
}
}
}

希望这能有所帮助。

下面是在Java 8中使用http://www.vavr.io/实现它的函数方法

import static io.vavr.API.*;
import static io.vavr.Predicates.instanceOf;
public Throwable liftRootCause(final Throwable throwable) {
return Match(throwable).of(
Case($(instanceOf(CompletionException.class)), Throwable::getCause),
Case($(instanceOf(ExecutionException.class)), Throwable::getCause),
Case($(), th -> th)
);
}

Eclipse建模框架有一个有趣的想法,它也考虑了继承。基本概念在Switch 接口中定义:通过调用doSwitch方法来完成切换。

真正有趣的是实现。对于每种类型的兴趣,a

public T caseXXXX(XXXX object);

方法必须实现(默认实现返回null)。doSwitch实现将尝试为对象的所有类型层次结构调用所有caseXXX方法。之类的东西:

BaseType baseType = (BaseType)object;
T result = caseBaseType(eAttribute);
if (result == null) result = caseSuperType1(baseType);
if (result == null) result = caseSuperType2(baseType);
if (result == null) result = caseSuperType3(baseType);
if (result == null) result = caseSuperType4(baseType);
if (result == null) result = defaultCase(object);
return result;

实际框架为每个类使用一个整数id,所以逻辑实际上是一个纯切换:

public T doSwitch(Object object) {
return doSwitch(object.class(), eObject);
}


protected T doSwitch(Class clazz, Object object) {
return doSwitch(getClassifierID(clazz), object);
}


protected T doSwitch(int classifierID, Object theObject) {
switch (classifierID) {
case MyClasses.BASETYPE:
{
BaseType baseType = (BaseType)object;
...
return result;
}
case MyClasses.TYPE1:
{
...
}
...

你可以查看ECoreSwitch的完整实现来获得更好的理解。

不幸的是,这是不可能开箱即用的,因为开关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
}

虽然不能编写switch语句,但可以为每个给定类型分支到特定的处理。一种方法是使用标准的双重分派机制。我们希望基于类型“切换”的一个示例是Jersey异常映射器,其中我们需要将大量异常映射到错误响应。虽然对于这种特定的情况可能有更好的方法(即使用多态方法将每个异常转换为错误响应),但使用双重分派机制仍然是有用和实用的。

interface Processable {
<R> R process(final Processor<R> processor);
}


interface Processor<R> {
R process(final A a);
R process(final B b);
R process(final C c);
// for each type of Processable
...
}


class A implements Processable {
// other class logic here


<R> R process(final Processor<R> processor){
return processor.process(this);
}
}


class B implements Processable {
// other class logic here


<R> R process(final Processor<R> processor){
return processor.process(this);
}
}


class C implements Processable {
// other class logic here


<R> R process(final Processor<R> processor){
return processor.process(this);
}
}

然后在任何需要“开关”的地方,你可以这样做:

public class LogProcessor implements Processor<String> {
private static final Logger log = Logger.for(LogProcessor.class);


public void logIt(final Processable base) {
log.info("Logging for type {}", process(base));
}


// Processor methods, these are basically the effective "case" statements
String process(final A a) {
return "Stringifying A";
}


String process(final B b) {
return "Stringifying B";
}


String process(final C c) {
return "Stringifying C";
}
}

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

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

如果你想避免if(){} else if{}的冗长,你可以考虑将这个文件切换到芬兰湾的科特林,并将类似切换的当表达式is操作符结合使用。

在任何情况下Kotlin和java文件可以在一个项目中共存和结果在一个jar可以运行在JVM。

when (this) { //switch-like statement in kotlin supporting class-pattern-matching and smart casts via `is` operator.
is A -> doA()
is B -> doB()
is C -> doC()
}

从Java 17开始,你可以使用模式匹配开关表达式(预览特性)中- 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

在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???");
}
}