在 Java 中避免 instanceof
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2790144/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Avoiding instanceof in Java
提问by Mark Lutton
Having a chain of "instanceof" operations is considered a "code smell". The standard answer is "use polymorphism". How would I do it in this case?
拥有一系列“instanceof”操作被认为是“代码异味”。标准答案是“使用多态”。在这种情况下我该怎么做?
There are a number of subclasses of a base class; none of them are under my control. An analogous situation would be with the Java classes Integer, Double, BigDecimal etc.
一个基类有许多子类;他们都不在我的控制之下。类似的情况是 Java 类 Integer、Double、BigDecimal 等。
if (obj instanceof Integer) {NumberStuff.handle((Integer)obj);}
else if (obj instanceof BigDecimal) {BigDecimalStuff.handle((BigDecimal)obj);}
else if (obj instanceof Double) {DoubleStuff.handle((Double)obj);}
I do have control over NumberStuff and so on.
我确实可以控制 NumberStuff 等等。
I don't want to use many lines of code where a few lines would do. (Sometimes I make a HashMap mapping Integer.class to an instance of IntegerStuff, BigDecimal.class to an instance of BigDecimalStuff etc. But today I want something simpler.)
我不想使用几行代码就可以使用多行代码。(有时我制作一个 HashMap 将 Integer.class 映射到 IntegerStuff 的实例,将 BigDecimal.class 映射到 BigDecimalStuff 的实例等等。但今天我想要更简单的东西。)
I'd like something as simple as this:
我想要像这样简单的东西:
public static handle(Integer num) { ... }
public static handle(BigDecimal num) { ... }
But Java just doesn't work that way.
但是 Java 不是那样工作的。
I'd like to use static methods when formatting. The things I'm formatting are composite, where a Thing1 can contain an array Thing2s and a Thing2 can contain an array of Thing1s. I had a problem when I implemented my formatters like this:
我想在格式化时使用静态方法。我正在格式化的东西是复合的,其中 Thing1 可以包含一个数组 Thing2s,一个 Thing2 可以包含一个 Thing1s 数组。当我像这样实现格式化程序时遇到了问题:
class Thing1Formatter {
private static Thing2Formatter thing2Formatter = new Thing2Formatter();
public format(Thing thing) {
thing2Formatter.format(thing.innerThing2);
}
}
class Thing2Formatter {
private static Thing1Formatter thing1Formatter = new Thing1Formatter();
public format(Thing2 thing) {
thing1Formatter.format(thing.innerThing1);
}
}
Yes, I know the HashMap and a bit more code can fix that too. But the "instanceof" seems so readable and maintainable by comparison. Is there anything simple but not smelly?
是的,我知道 HashMap 和更多的代码也可以解决这个问题。但是相比之下,“instanceof”似乎非常可读和可维护。有什么简单但不臭的东西吗?
Note added 5/10/2010:
2010 年 5 月 10 日添加的注释:
It turns out that new subclasses will probably be added in the future, and my existing code will have to handle them gracefully. The HashMap on Class won't work in that case because the Class won't be found. A chain of if statements, starting with the most specific and ending with the most general, is probably the best after all:
事实证明,将来可能会添加新的子类,我现有的代码将不得不优雅地处理它们。在这种情况下,Class 上的 HashMap 将不起作用,因为找不到 Class。一连串的 if 语句,从最具体的开始,到最一般的结束,可能是最好的:
if (obj instanceof SubClass1) {
// Handle all the methods and properties of SubClass1
} else if (obj instanceof SubClass2) {
// Handle all the methods and properties of SubClass2
} else if (obj instanceof Interface3) {
// Unknown class but it implements Interface3
// so handle those methods and properties
} else if (obj instanceof Interface4) {
// likewise. May want to also handle case of
// object that implements both interfaces.
} else {
// New (unknown) subclass; do what I can with the base class
}
采纳答案by DJClayworth
You might be interested in this entry from Steve Yegge's Amazon blog: "when polymorphism fails". Essentially he's addressing cases like this, when polymorphism causes more trouble than it solves.
您可能对 Steve Yegge 的 Amazon 博客中的这篇文章感兴趣:“when polymorphism failed”。从本质上讲,他是在处理这样的情况,当多态带来的麻烦比它解决的麻烦时。
The issue is that to use polymorphism you have to make the logic of "handle" part of each 'switching' class - i.e. Integer etc. in this case. Clearly this is not practical. Sometimes it isn't even logically the right place to put the code. He recommends the 'instanceof' approach as being the lesser of several evils.
问题是要使用多态性,您必须使每个“切换”类的“处理”部分的逻辑 - 即在这种情况下为 Integer 等。显然这是不切实际的。有时从逻辑上讲,它甚至不是放置代码的正确位置。他建议将“instanceof”方法作为多种弊端中的较小者。
As with all cases where you are forced to write smelly code, keep it buttoned up in one method (or at most one class) so that the smell doesn't leak out.
与您被迫编写臭味代码的所有情况一样,将其固定在一种方法(或至多一个类)中,以免异味泄漏。
回答by Chris Knight
As highlighted in the comments, the visitor pattern would be a good choice. But without direct control over the target/acceptor/visitee you can't implement that pattern. Here's one way the visitor pattern could possibly still be used here even though you have no direct control over the subclasses by using wrappers (taking Integer as an example):
正如评论中强调的那样,访问者模式将是一个不错的选择。但是如果没有对目标/接受者/访问者的直接控制,您就无法实现该模式。即使您无法通过使用包装器直接控制子类(以 Integer 为例),这里仍然可以使用访问者模式的一种方式:
public class IntegerWrapper {
private Integer integer;
public IntegerWrapper(Integer anInteger){
integer = anInteger;
}
//Access the integer directly such as
public Integer getInteger() { return integer; }
//or method passthrough...
public int intValue() { return integer.intValue(); }
//then implement your visitor:
public void accept(NumericVisitor visitor) {
visitor.visit(this);
}
}
Of course, wrapping a final class might be considered a smell of its own but maybe it's a good fit with your subclasses. Personally, I don't think instanceof
is that bad a smell here, especially if it is confined to one method and I would happily use it (probably over my own suggestion above). As you say, its quite readable, typesafe and maintainable. As always, keep it simple.
当然,包装一个 final 类可能被认为是它自己的味道,但也许它非常适合您的子类。就我个人而言,我认为instanceof
这里的气味并没有那么糟糕,特别是如果它仅限于一种方法并且我很乐意使用它(可能是根据我自己的建议)。正如您所说,它的可读性、类型安全性和可维护性很强。一如既往,保持简单。
回答by Aaron Digulla
Instead of a huge if
, you can put the instances you handle in a map (key: class, value: handler).
if
您可以将处理的实例放入映射(键:类,值:处理程序)中,而不是一个巨大的。
If the lookup by key returns null
, call a special handler method which tries to find a matching handler (for example by calling isInstance()
on every key in the map).
如果按键查找返回null
,则调用一个特殊的处理程序方法,该方法试图找到匹配的处理程序(例如,通过调用isInstance()
映射中的每个键)。
When a handler is found, register it under the new key.
找到处理程序后,将其注册到新密钥下。
This makes the general case fast and simple and allows you to handle inheritance.
这使得一般情况快速而简单,并允许您处理继承。
回答by Cowan
You could consider the Chain of Responsibility pattern. For your first example, something like:
您可以考虑责任链模式。对于您的第一个示例,例如:
public abstract class StuffHandler {
private StuffHandler next;
public final boolean handle(Object o) {
boolean handled = doHandle(o);
if (handled) { return true; }
else if (next == null) { return false; }
else { return next.handle(o); }
}
public void setNext(StuffHandler next) { this.next = next; }
protected abstract boolean doHandle(Object o);
}
public class IntegerHandler extends StuffHandler {
@Override
protected boolean doHandle(Object o) {
if (!o instanceof Integer) {
return false;
}
NumberHandler.handle((Integer) o);
return true;
}
}
and then similarly for your other handlers. Then it's a case of stringing together the StuffHandlers in order (most specific to least specific, with a final 'fallback' handler), and your despatcher code is just firstHandler.handle(o);
.
然后类似地用于您的其他处理程序。然后是按顺序将 StuffHandler 串在一起的情况(最具体到最不具体,带有最终的“后备”处理程序),并且您的调度程序代码只是firstHandler.handle(o);
.
(An alternative is to, rather than using a chain, just have a List<StuffHandler>
in your dispatcher class, and have it loop through the list until handle()
returns true).
(另一种方法是,而不是使用链,只List<StuffHandler>
在您的调度程序类中有一个,并让它循环遍历列表直到handle()
返回 true)。
回答by Jord?o
You can use reflection:
您可以使用反射:
public final class Handler {
public static void handle(Object o) {
try {
Method handler = Handler.class.getMethod("handle", o.getClass());
handler.invoke(null, o);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void handle(Integer num) { /* ... */ }
public static void handle(BigDecimal num) { /* ... */ }
// to handle new types, just add more handle methods...
}
You can expand on the idea to generically handle subclasses and classes that implement certain interfaces.
您可以扩展这个想法来一般处理实现某些接口的子类和类。
回答by Jose Martinez
Just go with the instanceof. All the workarounds seem more complicated. Here is a blog post that talks about it: http://www.velocityreviews.com/forums/t302491-instanceof-not-always-bad-the-instanceof-myth.html
只需使用instanceof。所有的解决方法似乎都更复杂。这是一篇谈论它的博客文章:http: //www.velocityreviews.com/forums/t302491-instanceof-not-always-bad-the-instanceof-myth.html
回答by Serge Rogatch
I think that the best solution is HashMap with Class as key and Handler as value. Note that HashMap based solution runs in constant algorithmic complexity θ(1), while the smelling chain of if-instanceof-else runs in linear algorithmic complexity O(N), where N is the number of links in the if-instanceof-else chain (i.e. the number of different classes to be handled). So the performance of HashMap based solution is asymptotically higher N times than the performance of if-instanceof-else chain solution. Consider that you need to handle different descendants of Message class differently: Message1, Message2, etc. . Below is the code snippet for HashMap based handling.
我认为最好的解决方案是 HashMap,以 Class 为键,以 Handler 为值。请注意,基于 HashMap 的解决方案以恒定算法复杂度 θ(1) 运行,而 if-instanceof-else 的嗅觉链以线性算法复杂度 O(N) 运行,其中 N 是 if-instanceof-else 链中的链接数(即要处理的不同类的数量)。所以基于 HashMap 的解决方案的性能比 if-instanceof-else 链解决方案的性能渐进高 N 倍。考虑到您需要以不同的方式处理 Message 类的不同后代:Message1、Message2 等。下面是基于 HashMap 处理的代码片段。
public class YourClass {
private class Handler {
public void go(Message message) {
// the default implementation just notifies that it doesn't handle the message
System.out.println(
"Possibly due to a typo, empty handler is set to handle message of type %s : %s",
message.getClass().toString(), message.toString());
}
}
private Map<Class<? extends Message>, Handler> messageHandling =
new HashMap<Class<? extends Message>, Handler>();
// Constructor of your class is a place to initialize the message handling mechanism
public YourClass() {
messageHandling.put(Message1.class, new Handler() { public void go(Message message) {
//TODO: IMPLEMENT HERE SOMETHING APPROPRIATE FOR Message1
} });
messageHandling.put(Message2.class, new Handler() { public void go(Message message) {
//TODO: IMPLEMENT HERE SOMETHING APPROPRIATE FOR Message2
} });
// etc. for Message3, etc.
}
// The method in which you receive a variable of base class Message, but you need to
// handle it in accordance to of what derived type that instance is
public handleMessage(Message message) {
Handler handler = messageHandling.get(message.getClass());
if (handler == null) {
System.out.println(
"Don't know how to handle message of type %s : %s",
message.getClass().toString(), message.toString());
} else {
handler.go(message);
}
}
}
More info on usage of variables of type Class in Java: http://docs.oracle.com/javase/tutorial/reflect/class/classNew.html
有关 Java 中 Class 类型变量用法的更多信息:http: //docs.oracle.com/javase/tutorial/reflect/class/classNew.html
回答by Ravindra babu
I have solved this problem using reflection
(around 15 years back in pre Generics era).
我已经使用reflection
(大约 15 年前的前泛型时代)解决了这个问题。
GenericClass object = (GenericClass) Class.forName(specificClassName).newInstance();
I have defined one Generic Class ( abstract Base class). I have defined many concrete implementations of base class. Each concrete class will be loaded with className as parameter. This class name is defined as part of configuration.
我定义了一个通用类(抽象基类)。我已经定义了许多基类的具体实现。每个具体类都将加载 className 作为参数。这个类名被定义为配置的一部分。
Base class defines common state across all concrete classes and concrete classes will modify the state by overriding abstract rules defined in base class.
基类定义所有具体类的公共状态,具体类将通过覆盖基类中定义的抽象规则来修改状态。
At that time, I don't know the name of this mechanism, which has been known as reflection
.
当时,我不知道这种机制的名称,它一直被称为 reflection
.
Few more alternatives are listed in this article: Map
and enum
apart from reflection.
几个备选方案中列出这文章:Map
与enum
从反射分开。