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

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

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

如何使用switch…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,而不是使用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() );
}

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

其他回答

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

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,则可以应用访问者模式来实现相同的目的。

如果需要通过“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或visitor模式。

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

ClassA、ClassB和ClassC实现了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块仍然可以工作。

还有一种更简单的方法来模拟使用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 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)
        );
    }