我正在开发一个应用程序,其中一个设计方法涉及到大量使用instanceof操作符。虽然我知道OO设计通常试图避免使用instanceof,但那是另一回事,这个问题纯粹与性能有关。我想知道是否对性能有影响?和==一样快吗?
例如,我有一个有10个子类的基类。在接受基类的单个函数中,我检查类是否是子类的实例并执行一些例程。
我想到的另一种解决方法是使用“type id”整数原语,并使用位掩码来表示子类的类别,然后对子类“type id”与表示类别的常量掩码进行位掩码比较。
instanceof是否被JVM优化得更快?我想坚持使用Java,但应用程序的性能至关重要。如果有人曾经在这条路上走过,可以提供一些建议,那就太棒了。我是不是太挑剔了,或者专注在错误的地方去优化?
我基于jmh-java-benchmark- prototype:2.21编写了一个性能测试。JDK为openjdk, version为1.8.0_212。测试机器是mac pro。
测试结果为:
Benchmark Mode Cnt Score Error Units
MyBenchmark.getClasses thrpt 30 510.818 ± 4.190 ops/us
MyBenchmark.instanceOf thrpt 30 503.826 ± 5.546 ops/us
结果表明:getClass优于instanceOf,这与其他测试结果相反。然而,我不知道为什么。
测试代码如下:
public class MyBenchmark {
public static final Object a = new LinkedHashMap<String, String>();
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean instanceOf() {
return a instanceof Map;
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean getClasses() {
return a.getClass() == HashMap.class;
}
public static void main(String[] args) throws RunnerException {
Options opt =
new OptionsBuilder().include(MyBenchmark.class.getSimpleName()).warmupIterations(20).measurementIterations(30).forks(1).build();
new Runner(opt).run();
}
}
很难说一个特定的JVM是如何实现实例的,但在大多数情况下,对象与结构相当,类也是如此,每个对象结构都有一个指向它是实例的类结构的指针。实际上是instanceof for
if (o instanceof java.lang.String)
可能和下面的C代码一样快
if (objectStruct->iAmInstanceOf == &java_lang_String_class)
假设JIT编译器已经就位,并且工作出色。
考虑到这只是访问一个指针,在指针指向的某个偏移量处获得一个指针,并将其与另一个指针进行比较(这基本上与测试32位数字是否相等相同),我认为操作实际上可以非常快。
但是,这并不一定是必须的,它在很大程度上取决于JVM。但是,如果这将成为代码中的瓶颈操作,我认为JVM实现相当糟糕。即使没有JIT编译器,只解释代码,也应该能够在几乎没有时间的情况下创建一个实例测试。
德米安和保罗提到了一个很好的观点;然而,要执行的代码的位置实际上取决于你想如何使用数据……
我非常喜欢可以以多种方式使用的小型数据对象。如果你采用覆盖(多态)方法,你的对象只能被“一种方式”使用。
这就是模式发挥作用的地方……
您可以使用双重分派(如在访问者模式中)要求每个对象传递自身“调用您”——这将解析对象的类型。但是(再次),您需要一个可以对所有可能的子类型“做一些事情”的类。
我更喜欢使用策略模式,在这种模式下,您可以为想要处理的每个子类型注册策略。大致如下。注意,这只有助于精确的类型匹配,但它具有可扩展的优势——第三方贡献者可以添加自己的类型和处理程序。(这对于OSGi这样的动态框架很好,可以添加新的包)
希望这能激发一些其他的想法……
package com.javadude.sample;
import java.util.HashMap;
import java.util.Map;
public class StrategyExample {
static class SomeCommonSuperType {}
static class SubType1 extends SomeCommonSuperType {}
static class SubType2 extends SomeCommonSuperType {}
static class SubType3 extends SomeCommonSuperType {}
static interface Handler<T extends SomeCommonSuperType> {
Object handle(T object);
}
static class HandlerMap {
private Map<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>> handlers_ =
new HashMap<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>>();
public <T extends SomeCommonSuperType> void add(Class<T> c, Handler<T> handler) {
handlers_.put(c, handler);
}
@SuppressWarnings("unchecked")
public <T extends SomeCommonSuperType> Object handle(T o) {
return ((Handler<T>) handlers_.get(o.getClass())).handle(o);
}
}
public static void main(String[] args) {
HandlerMap handlerMap = new HandlerMap();
handlerMap.add(SubType1.class, new Handler<SubType1>() {
@Override public Object handle(SubType1 object) {
System.out.println("Handling SubType1");
return null;
} });
handlerMap.add(SubType2.class, new Handler<SubType2>() {
@Override public Object handle(SubType2 object) {
System.out.println("Handling SubType2");
return null;
} });
handlerMap.add(SubType3.class, new Handler<SubType3>() {
@Override public Object handle(SubType3 object) {
System.out.println("Handling SubType3");
return null;
} });
SubType1 subType1 = new SubType1();
handlerMap.handle(subType1);
SubType2 subType2 = new SubType2();
handlerMap.handle(subType2);
SubType3 subType3 = new SubType3();
handlerMap.handle(subType3);
}
}