我有一个关于使用switch case处理 instanceof
对象的问题:
例如:我的问题可以在Java中重现:
if(this instanceof A)
doA();
else if(this instanceof B)
doB();
else if(this instanceof C)
doC():
如何使用 switch...case
实现它?
我有一个关于使用switch case处理 instanceof
对象的问题:
例如:我的问题可以在Java中重现:
if(this instanceof A)
doA();
else if(this instanceof B)
doB();
else if(this instanceof C)
doC():
如何使用 switch...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() } ... }
那么你可以在this
上简单调用do()
。
如果你无法自由更改A
、B
和C
,你可以应用访问者模式来实现相同的功能。
如果你绝对无法编写接口代码,那么你可以使用枚举作为中介:
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;
}
case A
、case B
和case C
中类名的自动重命名。 - logi-kalMap
,其中键是 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
。
下面是一个真实的示例,展示了这种方法如何简化代码。
重构为 map 之前的代码:
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() );
}
这样可以避免重复,消除几乎所有的分支语句,并简化维护。
instanceof
,但不幸的是,我的情况不是那种容易适应这种方法的情况...)。 - Per LundbergClass<ArrayList>
将无法匹配Class<List>
。 - Gili现在Java允许您以OP的方式进行切换。他们称之为Pattern Matching for switch。它是作为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();
};
无论哪种方式,他们一直在使用开关做很酷的事情。
以防万一有人会读到:
Java中最好的解决方案是:
public enum Action {
a{
void doAction(...){
// some code
}
},
b{
void doAction(...){
// some code
}
},
c{
void doAction(...){
// some code
}
};
abstract void doAction (...);
}
这种模式的优点如下:
你只需要像这样实现(完全没有switch语句):
void someFunction ( Action action ) {
action.doAction(...);
}
如果你添加了名为"d"的新操作,你必须实现doAction(...)方法。
注意:这个模式在Joshua Bloch的《Effective Java (第二版)》中有描述。
doAction()
的实现上方是否需要使用@Override
? - mateuscbsomeFunction()
吗?这只是增加了另一层间接性。 - PureSpider你无法这样做。 switch
语句只能包含编译时常量且计算结果为整数的 case
语句(Java6及以下版本)或字符串(Java7)。
你需要的是函数式编程中所谓的 "模式匹配"。
如前面的答案所讨论的那样,传统的OOP方法是使用多态而不是switch语句。甚至有一个经过充分文档化的重构模式来实现这个技巧:用多态替换条件表达式。每当我使用这种方法时,我还喜欢实现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 classes。
很遗憾,由于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
}
Map
是只读的,并且仅由一个线程初始化,因此从我的观点来看,应该优先选择简单的HashMap
。 - Glainsjava 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:
equals()
方法的实现中保持对称性。此外,在许多情况下,您可以重写代码以使用多态性,而不是instanceof
。然而仍然存在一些有效的用例。 - Rik Schaafjava.awt.Component#process...
方法,告诉我在哪里可以使用多态。我个人在类似情况下使用哈希映射,但问题是关于switch语句的,我相信这可能有性能原因,因为即使哈希映射很快,switch更快。 - Mikeswitch
和HashMap
内联提及。 - Mike我知道这已经很晚了,但对于未来的读者……
要注意上述方法仅基于A、B、C……类的名称:
除非您可以保证A、B、C……(所有Base的子类或实现者)都是final,否则将无法处理A、B、C……的子类。
即使if、elseif、elseif......的方法在大量子类/实现者时速度较慢,但更准确。