切换实例?

oli*_*dev 249 java instanceof switch-statement

我有一个使用switch case for instanceofobject的问题:

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

if(this instanceof A)
    doA();
else if(this instanceof B)
    doB();
else if(this instanceof C)
    doC():
Run Code Online (Sandbox Code Playgroud)

如何使用switch...case

jmg*_*jmg 209

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

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() } ... }
Run Code Online (Sandbox Code Playgroud)

然后,你可以简单的调用do()this.

如果你不随意更改A,BC,你可以申请访问者模式来实现相同的.

  • 访问者模式意味着A,B和C必须使用抽象方法实现接口,该方法将Visitor作为输入参数,如果您不能更改A,B,C并且它们都没有实现该接口,该怎么办? (31认同)
  • 关于访客模式的最后评论是错误的.您仍然需要使A,B和C实现一个接口. (19认同)
  • 遗憾的是,如果do() - Code需要主机的环境(即访问do()本身不存在的变量),这不起作用. (8认同)
  • 这个答案假设您可以修改类A,B,C,而我认为重点是如何在不修改A,B,C的情况下执行此操作,因为它们可能位于第三方库中 (3认同)
  • 在给出这个答案 11 年后,Java 17 终于实现了操作者想要的功能。请参阅下面有关模式匹配的答案/sf/answers/4020256901/ (3认同)
  • @mafu OP的问题是关于基于类型的调度.如果你的do()方法需要更多输入才能发送,那么你的问题就是恕我直言,这里讨论的问题范围之外. (2认同)
  • 很晚了,但就像 daniele 说的:是的,这种方法对于检查泛型集合中的类型是没有用的,例如,你有一组你知道的对象,可以是某些类型的集合,但你必须检查它们中的哪些这是。 (2认同)

Nic*_*ico 92

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

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;

}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,我还必须做出一些更改:1)使用类引用初始化每个枚举 id;2) 使用枚举 id 断言类简单名称 .toString(); 3)通过每个枚举 ID 存储的类引用查找枚举。我认为这也是混淆安全的。 (2认同)

se.*_*yev 34

以防万一有人会阅读它:

Java中的最佳解决方案是:

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

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

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

    };

    abstract void doAction (...);
}
Run Code Online (Sandbox Code Playgroud)

这种模式的好处是:

  1. 你就是这样(根本没有开关):

    void someFunction ( Action action ) {
        action.doAction(...);   
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 如果你添加名为"d"的新Action,你必须使用imAction(...)方法

注意:这种模式在Joshua的Bloch"Effective Java(第2版)"中有所描述.

  • 我不明白这一点.你怎么知道使用哪个枚举?正如@PureSpider所说,这似乎只是另一个层次的工作要做. (10认同)
  • 这是"最佳"解决方案怎么样?您如何决定使用哪种"动作"?通过一个外部的instanceof-cascade,用正确的`action`调用`someFunction()`?这只是增加了另一层次的间接性. (7认同)
  • 非常遗憾的是您**没有提供完整的示例**,例如如何将 a、b 或 C 的任何类实例映射到此枚举。我将尝试将实例转换为该枚举。 (2认同)

Nov*_*ata 32

只需创建一个Map,其中类是键,功能,即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());
Run Code Online (Sandbox Code Playgroud)

//当然,重构一下只能初始化一次

doByClass.get(getClass()).run();
Run Code Online (Sandbox Code Playgroud)

如果您需要检查异常,而不是实现抛出异常的FunctionalInterface并使用它而不是Runnable.

  • 已投赞成票;这是实际上帮助OP完成他所要求的事情的少数答案之一(是的,通常可以重构代码以不必执行“instanceof”,不,不幸的是,我的场景不是其中之一很容易就能装进那个盒子......) (5认同)
  • 最好的解决方案imho,特别是因为它允许轻松重构. (3认同)
  • 该解决方案的唯一缺点是,它无法利用lambda中的局部(方法)变量(当然当然需要使用此变量)。 (2认同)

Car*_*ngo 18

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

您正在寻找的是在函数式编程中称为"模式匹配".

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


Pav*_*vel 16

正如在顶部答案中所讨论的,传统的OOP方法是使用多态而不是切换.对于这个技巧,甚至有一个记录良好的重构模式:用多态替换条件.每当我达到这种方法时,我也想实现一个Null对象来提供默认行为.

从Java 8开始,我们可以使用lambdas和泛型来为我们提供一些函数式程序员非常熟悉的东西:模式匹配.它不是核心语言功能,但Javaslang库提供了一个实现.来自javadoc的示例:

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

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


A_A*_*old 9

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

或者使用他们的 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();
    };
Run Code Online (Sandbox Code Playgroud)

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

  • 这仅适用于 Java 17 的预览模式。 (13认同)
  • @A_Arnold“这是一个预览功能,该功能的设计、规范和实现都是完整的,但不是永久的,这意味着该功能可能以不同的形式存在,或者在未来的 Java SE 版本中根本不存在。” 参考:https://docs.oracle.com/en/java/javase/17/language/pattern-matching-switch-expressions-and-statements.html (4认同)
  • @olidev 考虑将此作为(新)接受的答案。 (3认同)
  • 这是 Java 17 中的预览版功能,其语法可能会发生变化,并且必须使用 VM 参数打开。 (3认同)
  • Java 21 现已推出,现已是最终版本,不再是预览功能。 (3认同)

Joh*_*hnK 8

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

注意上面仅基于A,B,C ...... 类的名称的方法:

除非你能保证A,B,C ......(Base的所有子类或实现者)都是最终的,否则A,B,C ......的子类将不会被处理.

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


Myk*_*ych 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());
    }
}
Run Code Online (Sandbox Code Playgroud)

getSimpleName通过引入常量和使用完整的类名省略内部的字符串操作,您可以更快:

public static final TRADE = Trade.class.getName();
...
switch (model.getClass().getName()) {
case TRADE:
Run Code Online (Sandbox Code Playgroud)

  • 这和做instanceof是不一样的,因为这只在实现类切换时才有效,但对interfaces/abstractclass/superclasses不起作用 (3认同)

Tne*_*nem 5

你不能让 switch 只适用于 byte、short、char、int、String 和枚举类型(以及基元的对象版本,它还取决于你的 java 版本,Strings 可以switch在 java 7 中使用)


And*_*son 5

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


Rae*_*ald 5

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

this.do()
Run Code Online (Sandbox Code Playgroud)

之前已经设置了基类:

abstract class Base {
   abstract void do();
   ...
}
Run Code Online (Sandbox Code Playgroud)

这是基类A,BC:

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

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

class C extends Base {
    void do() { this.doC() }
}
Run Code Online (Sandbox Code Playgroud)


Fei*_*ira 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");
            });
Run Code Online (Sandbox Code Playgroud)

将输出:

YYyy
Run Code Online (Sandbox Code Playgroud)

示例代码使用字符串,但您可以使用任何对象类型,包括类。例如.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);
}
Run Code Online (Sandbox Code Playgroud)


Mur*_*göz 5

不幸的是,由于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);
 }
}
Run Code Online (Sandbox Code Playgroud)

这样就可以使用这样的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
}
Run Code Online (Sandbox Code Playgroud)