325

我有一个使用开关盒作为instanceof对象的问题:

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

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

它将如何使用switch...case

4

24 回答 24

254

这是子类型多态性有帮助的典型场景。请执行下列操作

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

如果您不能随意更改ABC,则可以应用访问者模式来实现相同的目的。

于 2011-04-07T10:09:24.747 回答
112

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

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;

}
于 2011-04-07T11:38:58.487 回答
60

创建一个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() );
}

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

于 2017-02-28T15:50:14.660 回答
40

以防万一有人读到它:

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. 如果您添加名为“d”的新操作,您必须实施 doAction(...) 方法

注意:这种模式在 Joshua 的 Bloch “Effective Java (2nd Edition)”中有描述

于 2011-10-10T10:07:24.453 回答
27

你不能。该switch语句只能包含case编译时常量且计算结果为整数的语句(Java 6 和 Java 7 中的字符串)。

您正在寻找的是函数式编程中的“模式匹配”。

另请参阅避免在 Java 中使用 instanceof

于 2011-04-07T10:06:41.127 回答
20

正如上面的答案中所讨论的,传统的 OOP 方法是使用多态性而不是 switch。这个技巧甚至有一个有据可查的重构模式:Replace Conditional with Polymorphism。每当我采用这种方法时,我也喜欢实现一个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 的案例类

于 2015-09-09T06:37:34.820 回答
12

不幸的是,这是不可能的,因为 switch-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
}
于 2019-03-27T15:21:23.067 回答
10

我知道这已经很晚了,但对于未来的读者......

请注意上述仅基于ABC ...类名称的方法:

除非您可以保证ABC ...( Base的所有子类或实现者)是final ,否则ABC ... 的子类将不会被处理。

即使if, elseif, elseif .. 方法对于大量子类/实现者来说速度较慢,但​​它更准确。

于 2014-02-27T10:50:23.993 回答
10

Java 现在允许您以 OP 的方式进行切换。他们称之为开关的模式匹配。它目前处于草稿阶段,但看看他们最近在交换机上投入了多少工作,我认为它会通过。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();
    };

无论哪种方式,他们一直在用开关做很酷的事情。

于 2019-08-09T14:32:31.763 回答
8

爪哇 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:
于 2018-06-13T11:47:45.037 回答
6

不,没有办法做到这一点。然而,您可能想要做的是将多态性视为处理此类问题的一种方式。

于 2011-04-07T10:09:28.960 回答
5

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

this.do()

之前设置了一个基类:

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

这是A,B和的基类C

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

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

class C extends Base {
    void do() { this.doC() }
}
于 2011-04-07T10:10:54.887 回答
5

我个人喜欢下面的 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

示例代码使用字符串,但您可以使用任何对象类型,包括类。例如.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);
}
于 2017-11-13T16:07:24.353 回答
4

您不能仅使用 byte、short、char、int、String 和枚举类型(以及原语的对象版本,它还取决于您的 java 版本,Strings 可以switch在 java 7 中编辑)

于 2011-04-07T10:06:01.933 回答
3

如果您可以操作通用接口,您可以添加一个枚举并让每个类返回一个唯一值。您不需要 instanceof 或访问者模式。

对我来说,逻辑需要写在 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;
  }

实施:

...
  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 块仍然可以工作。

于 2013-10-08T15:45:50.630 回答
2

这个怎么样 ?

switch (this.name) 
{
  case "A":
    doA();
    break;
  case "B":
    doB();
    break;
  case "C":
    doC();
    break;
  default:
    console.log('Undefined instance');
}
于 2012-11-21T10:25:48.277 回答
1

我认为有理由使用 switch 语句。如果您使用的是 xText 生成的代码,也许。或另一种 EMF 生成的类。

instance.getClass().getName();

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

instance.getClass().getSimpleName();

返回简单的表示,即:EcoreUtil

于 2013-04-02T08:03:11.827 回答
1

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

但是,如果您需要将“开关”应用于任何其他变量。我会建议另一种解决方案。定义如下接口:

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”语句的编译时错误。

于 2017-03-31T07:49:48.860 回答
1

使用类名创建一个枚举。

public enum ClassNameEnum {
    A, B, C
}

找到对象的类名。在枚举上写一个switch 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;
        }
    }
}

希望这可以帮助。

于 2018-06-27T05:21:30.743 回答
1

这是使用http://www.vavr.io/在 Java 8 中实现它的一种功能方式

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)
        );
    }
于 2018-07-19T08:30:19.980 回答
1

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

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";
    }
}
于 2019-04-15T18:28:21.017 回答
0

还有一种更简单的方法可以模拟使用 instanceof 的 switch 结构,您可以通过在方法中创建代码块并用标签对其进行命名来做到这一点。然后使用 if 结构来模拟 case 语句。如果一个案例是真的,那么你使用 break 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;
            }
        }
于 2017-06-26T12:38:39.427 回答
0

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

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

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的完整实现以获得更好的想法。

于 2019-03-07T16:43:03.803 回答
0

如果你想避免冗长if(){} else if{},你可以考虑将这个单个文件切换到kotlin并使用 switch-like when 表达式结合is运算符。

在任何情况下,Kotlin 和 java 文件都可以在一个项目中共存,并生成一个可以在 JVM 中运行的 jar。

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()
}
于 2020-07-06T17:51:24.947 回答