【问题标题】:The performance impact of using instanceof in Java在 Java 中使用 instanceof 对性能的影响
【发布时间】:2010-09-11 08:09:44
【问题描述】:

我正在开发一个应用程序,其中一种设计方法涉及大量使用 instanceof 运算符。虽然我知道 OO 设计通常会尽量避免使用 instanceof,但这是另一回事,这个问题完全与性能有关。我想知道是否有任何性能影响?和==一样快吗?

例如,我有一个包含 10 个子类的基类。在接受基类的单个函数中,我会检查该类是否是子类的实例并执行一些例程。

我想到的解决它的其他方法之一是改用“type id”整数原语,并使用位掩码来表示子类的类别,然后对子类“type id”进行位掩码比较" 到表示类别的常量掩码。

instanceof 是否被 JVM 优化为比这更快?我想坚持使用 Java,但应用程序的性能至关重要。如果以前曾经走过这条路的人可以提供一些建议,那就太酷了。我是不是吹毛求疵,还是专注于错误的优化?

【问题讨论】:

  • 我认为问题的重点是搁置最佳 OO 实践的问题,并检查性能。
  • @Dave L. 通常我会同意,但 OP 确实提到他正在寻找一些通用的优化技术,他不确定他的问题是否与“instanceof”有关。我认为至少值得一提的是“正确”的设计,这样他就可以分析两种选择。
  • 呃......为什么所有的答案都没有抓住问题的重点,并提供了关于优化的同样古老的 Knuth 修辞?您的问题是关于 instanceof 是否比使用 == 检查类对象明显/令人惊讶地慢,而我发现事实并非如此。
  • instanceof 和 cast 的性能相当不错。我在 Java7 中发布了一些关于解决问题的不同方法的时间:stackoverflow.com/questions/16320014/…
  • equals(Object) 的正确实现必须使用instanceof,并且equals(Object) 的调用非常普遍,所以我希望大多数JVM 可以非常有效地做到这一点。

标签: java performance instanceof


【解决方案1】:

方法

我写了a benchmark program 来评估不同的实现:

  1. instanceof 实现(作为参考)
  2. 通过抽象类和@Override 测试方法面向对象
  3. 使用自己的类型实现
  4. getClass() == _.class 实现

我使用jmh 运行基准测试,其中包含 100 次预热调用、1000 次测量迭代和 10 次分叉。因此,每个选项都测量了 10 000 次,这需要 12:18:57 在我的 MacBook Pro 上运行 macOS 10.12.4 和 Java 1.8 的整个基准测试。基准测量每个选项的平均时间。更多详情请见my implementation on GitHub

为了完整起见:有一个previous version of this answer and my benchmark

结果

|操作 |每次操作的运行时间(以纳秒为单位)|相对于 instanceof | |------------|------------------------------------ --|------------------------| |实例 | 39,598 ± 0,022 ns/操作 | 100,00 % | |获取类 | 39,687 ± 0,021 ns/操作 | 100,22 % | |类型 | 46,295 ± 0,026 纳秒/操作 | 116,91 % | |面向对象 | 48,078 ± 0,026 ns/操作 | 121,42 % |

tl;博士

在 Java 1.8 中,instanceof 是最快的方法,尽管 getClass() 非常接近。

【讨论】:

  • +0.(9) 科学!
  • + 我的其他 0.1 :D
  • @TobiasReich 所以我们得到了+1.0(9)。 :)
  • 我认为这根本没有任何意义。该代码使用System.currentTimeMillis() 对一个操作进行测量,该操作只不过是一个方法调用,这应该会给低精度带来很多影响。请改用 JMH 等基准框架!
  • 或者只计算整个十亿次调用的时间,而不是每次调用。
【解决方案2】:

现代 JVM/JIT 编译器已经消除了大多数传统上“慢”操作的性能损失,包括 instanceof、异常处理、反射等。

正如 Donald Knuth 所写,“我们应该忘记小的效率,比如大约 97% 的时间:过早的优化是万恶之源。” instanceof 的性能可能不会成为问题,所以在确定问题所在之前不要浪费时间想出奇特的解决方法。

【讨论】:

  • 现代 JVM/JIC ..能否请您提及这些优化是从哪个 Java 版本开始的?
  • 总是有人在以性能为主题时引用 Knuth...忘记了,Knuth 也说过(在同一篇文章中)“在已建立的工程学科中,12% 的改进很容易获得,但永远不会被认为是边缘的,我相信同样的观点应该在软件工程中占上风”,他几乎所有的工作都是关于算法的效率,他在汇编中编写算法以(除其他外)获得更好的性能。嗯……
  • 抛开这里,但try { ObjT o = (ObjT)object } catch (e) { no not one of these } 会更快更慢吗??
  • 如果“object”是 ObjT 的一个实例,投射它比做一个 instanceof 快一点,但我的快速测试发现差异是 10-20 毫秒超过 10,000,000 次迭代。但是,如果“object”不是 ObjT,则捕获异常的速度会慢 3000 倍以上 - 超过 31,000 毫秒,而 instanceof 则约为 10 毫秒。
  • 这么有力的论据没有任何“参考”,完全没用,因为只是固执己见。
【解决方案3】:

我刚刚做了一个简单的测试,看看 instanceOf 的性能如何与对只有一个字母的字符串对象的简单 s.equals() 调用进行比较。

在 10.000.000 循环中,instanceOf 给了我 63-96ms,而字符串 equals 给了我 106-230ms

我用的是 java jvm 6。

所以在我的简单测试中,执行 instanceOf 而不是一个字符串比较更快。

使用整数的 .equals() 而不是字符串给了我相同的结果,只有当我使用 == i 时,我比 instanceOf 快 20 毫秒(在 10.000.000 循环中)

【讨论】:

  • 您可以在这里发布代码吗?那太棒了!
  • instanceOf 与多态函数调度相比如何?
  • 为什么将 instanceof 与 String.equals() 进行比较?如果要检查类型,则必须使用 object.getClass().equals(SomeType.class)
  • @marsbear equals() 不会削减它,因为子类化;你需要isAssignableFrom()
  • @marsbear 是的,但这并不能更好地测试 OP 的要求。
【解决方案4】:

决定性能影响的项目有:

  1. instanceof 运算符可以为其返回 true 的可能类的数量
  2. 您的数据分布 - 大多数 instanceof 操作是否在第一次或第二次尝试中解决?您需要将最有可能返回真正的操作放在首位。
  3. 部署环境。在 Sun Solaris VM 上运行与 Sun 的 Windows JVM 有很大不同。默认情况下,Solaris 将在“服务器”模式下运行,而 Windows 将在客户端模式下运行。 Solaris 上的 JIT 优化将使所有方法的访问权限都相同。

我创建了一个microbenchmark for four different methods of dispatch。 Solaris的结果如下,数字越小越快:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 

【讨论】:

    【解决方案5】:

    回答你的最后一个问题:除非分析器告诉你,你在一个实例中花费了荒谬的时间:是的,你在吹毛求疵。

    在考虑优化从未需要优化的内容之前:以最易读的方式编写算法并运行它。运行它,直到 jit 编译器有机会自己优化它。如果您在这段代码中遇到问题,请使用分析器告诉您,从哪里获得最大收益并对其进行优化。

    在高度优化编译器的时代,您对瓶颈的猜测很可能完全错误。

    并且本着这个答案的真实精神(我完全相信):一旦 jit 编译器有机会优化它,我绝对不知道 instanceof 和 == 之间的关系。

    我忘记了:从不测量第一次运行。

    【讨论】:

    • 但是原发帖人提到性能对于这个应用程序来说很关键,所以在这种情况下尽早优化并不是不合理的。换句话说,你不会在 GWBasic 中编写 3d 游戏,然后在最后说,好吧,让我们开始优化吧,第一步是将其移植到 c++。
    • 如果有适当的库可用,GWBasic 可能是 3d 游戏的一个很好的开始。但除此之外(因为这是一个人为的论点):OP 并没有要求完全重写作为优化。它是关于单个构造,我们甚至不知道影响是否显着(即使 在当前版本的编译器中有更好的执行方式来执行相同的操作)。我坚定地支持c2.com/cgi/wiki?ProfileBeforeOptimizing 和我的回答。前期优化是万恶之源!它使维护变得更加困难-维护是值得优化的方面
    【解决方案6】:

    我也有同样的问题,但是因为我没有找到与我类似的用例的“性能指标”,所以我做了更多的示例代码。在我的硬件和 Java 6 和 7 上,instanceof 和 switch on 1000 万次迭代之间的区别是

    for 10 child classes - instanceof: 1200ms vs switch: 470ms
    for 5 child classes  - instanceof:  375ms vs switch: 204ms
    

    因此,instanceof 确实比较慢,尤其是在大量 if-else-if 语句上,但在实际应用中差异可以忽略不计。

    import java.util.Date;
    
    public class InstanceOfVsEnum {
    
        public static int c1, c2, c3, c4, c5, c6, c7, c8, c9, cA;
    
        public static class Handler {
            public enum Type { Type1, Type2, Type3, Type4, Type5, Type6, Type7, Type8, Type9, TypeA }
            protected Handler(Type type) { this.type = type; }
            public final Type type;
    
            public static void addHandlerInstanceOf(Handler h) {
                if( h instanceof H1) { c1++; }
                else if( h instanceof H2) { c2++; }
                else if( h instanceof H3) { c3++; }
                else if( h instanceof H4) { c4++; }
                else if( h instanceof H5) { c5++; }
                else if( h instanceof H6) { c6++; }
                else if( h instanceof H7) { c7++; }
                else if( h instanceof H8) { c8++; }
                else if( h instanceof H9) { c9++; }
                else if( h instanceof HA) { cA++; }
            }
    
            public static void addHandlerSwitch(Handler h) {
                switch( h.type ) {
                    case Type1: c1++; break;
                    case Type2: c2++; break;
                    case Type3: c3++; break;
                    case Type4: c4++; break;
                    case Type5: c5++; break;
                    case Type6: c6++; break;
                    case Type7: c7++; break;
                    case Type8: c8++; break;
                    case Type9: c9++; break;
                    case TypeA: cA++; break;
                }
            }
        }
    
        public static class H1 extends Handler { public H1() { super(Type.Type1); } }
        public static class H2 extends Handler { public H2() { super(Type.Type2); } }
        public static class H3 extends Handler { public H3() { super(Type.Type3); } }
        public static class H4 extends Handler { public H4() { super(Type.Type4); } }
        public static class H5 extends Handler { public H5() { super(Type.Type5); } }
        public static class H6 extends Handler { public H6() { super(Type.Type6); } }
        public static class H7 extends Handler { public H7() { super(Type.Type7); } }
        public static class H8 extends Handler { public H8() { super(Type.Type8); } }
        public static class H9 extends Handler { public H9() { super(Type.Type9); } }
        public static class HA extends Handler { public HA() { super(Type.TypeA); } }
    
        final static int cCycles = 10000000;
    
        public static void main(String[] args) {
            H1 h1 = new H1();
            H2 h2 = new H2();
            H3 h3 = new H3();
            H4 h4 = new H4();
            H5 h5 = new H5();
            H6 h6 = new H6();
            H7 h7 = new H7();
            H8 h8 = new H8();
            H9 h9 = new H9();
            HA hA = new HA();
    
            Date dtStart = new Date();
            for( int i = 0; i < cCycles; i++ ) {
                Handler.addHandlerInstanceOf(h1);
                Handler.addHandlerInstanceOf(h2);
                Handler.addHandlerInstanceOf(h3);
                Handler.addHandlerInstanceOf(h4);
                Handler.addHandlerInstanceOf(h5);
                Handler.addHandlerInstanceOf(h6);
                Handler.addHandlerInstanceOf(h7);
                Handler.addHandlerInstanceOf(h8);
                Handler.addHandlerInstanceOf(h9);
                Handler.addHandlerInstanceOf(hA);
            }
            System.out.println("Instance of - " + (new Date().getTime() - dtStart.getTime()));
    
            dtStart = new Date();
            for( int i = 0; i < cCycles; i++ ) {
                Handler.addHandlerSwitch(h1);
                Handler.addHandlerSwitch(h2);
                Handler.addHandlerSwitch(h3);
                Handler.addHandlerSwitch(h4);
                Handler.addHandlerSwitch(h5);
                Handler.addHandlerSwitch(h6);
                Handler.addHandlerSwitch(h7);
                Handler.addHandlerSwitch(h8);
                Handler.addHandlerSwitch(h9);
                Handler.addHandlerSwitch(hA);
            }
            System.out.println("Switch of - " + (new Date().getTime() - dtStart.getTime()));
        }
    }
    

    【讨论】:

    • 哪个结果是 java 6,哪个是 java 7?您是否在 Java 8 下重新访问过这个?更重要的是,您将 instanceofs 的 if 长度与 int 上的 case 语句的基本内容进行比较。我认为我们希望 int 开关能够快速发光。
    • 我不记得 5 年前到底发生了什么——我认为 Java 6 和 Java 7 的结果相似,这就是为什么只提供一个结果(假设 2 行是针对不同深度的类层次结构)...不,我没有尝试与 Java 8 进行比较。提供了整个测试代码 - 您可以复制/粘贴它并检查您需要的环境(注意 - 现在我会为此使用 JMH 测试)。
    • 此代码未正确预热 VM,这将使第一个循环膨胀。这似乎扭曲了结果。
    【解决方案7】:

    instanceof 非常快,只占用几条 CPU 指令。

    显然,如果一个类X 没有加载子类(JVM 知道),instanceof 可以优化为:

         x instanceof X    
    ==>  x.getClass()==X.class  
    ==>  x.classID == constant_X_ID
    

    主要成本只是阅读!

    如果X 确实加载了子类,则需要更多读取;它们很可能位于同一地点,因此额外费用也非常低。

    大家好消息!

    【讨论】:

    • 可以优化还是优化?来源?
    • @vaxquis 可以作为它的 jvm impl 特定
    • @itzJanuary sigh 你在这里错过了我的问题的重点:每个人都知道编译器可以优化foo - 但是@ 987654327@ 实际上目前由 Oracle 的 javac/VM 优化 - 或者它是否有可能在未来这样做? 另外,我问回答者 他是否有任何支持来源(无论是文档,源代码,开发博客)记录它确实可以优化或优化?没有它,这个答案只是对编译器可以可能做什么的一些随机思考。
    • @vaxquis 您从未提到过 Hotspot VM,但在那种情况下,我不知道它是否已“优化”。
    • 最近读到 JIT (JVM 8) 将通过直接调用为 1 或 2 种类型优化调用站点,但如果遇到超过两种实际类型,则恢复为 vtable。因此,只有在运行时通过调用站点的两种具体类型代表了性能优势。
    【解决方案8】:

    在大多数现实世界的实现中,instanceof 可能会比简单的 equals 更昂贵(即,真正需要 instanceof 的那些,你不能像每个初学者教科书一样通过覆盖一个通用方法来解决它以及上面的 Demian 建议)。

    这是为什么呢?因为可能会发生的是你有几个接口,它们提供一些功能(比如说,接口 x、y 和 z),以及一些要操作的对象可能(或不)实现这些接口之一......但是不直接。比如说,我有:

    w 扩展 x

    A 实现 w

    B 扩展 A

    C 扩展 B,实现 y

    D 扩展 C,实现 z

    假设我正在处理 D 的一个实例,即对象 d。计算 (d instanceof x) 需要采用 d.getClass(),循环遍历它实现的接口以了解一个是否 == 到 x,如果不是,则递归地对其所有祖先再次这样做...... 在我们的例子中,如果你对该树进行广度优先探索,至少会产生 8 次比较,假设 y 和 z 不扩展任何东西......

    现实世界的派生树的复杂性可能更高。在某些情况下,如果 JIT 能够提前将 d 解析为在所有可能的情况下扩展 x 的某个实例,则 JIT 可以将其中的大部分优化掉。然而,实际上,您大部分时间都将经历该树遍历。

    如果这成为一个问题,我建议改用处理程序映射,将对象的具体类链接到执行处理的闭包。它删除了树遍历阶段,有利于直接映射。但是请注意,如果您为 C.class 设置了处理程序,则无法识别我上面的对象 d。

    这是我的 2 美分,希望对您有所帮助...

    【讨论】:

      【解决方案9】:

      Instanceof 非常快。它归结为用于类引用比较的字节码。在循环中尝试几百万个实例,然后自己看看。

      【讨论】:

        【解决方案10】:

        instanceof 非常高效,因此您的性能不太可能受到影响。 但是,使用大量 instanceof 会导致设计问题。

        如果您可以使用 xClass == String.class,这会更快。注意:final 类不需要 instanceof。

        【讨论】:

        • 顺便说一句,“最终课程不需要 instanceof”是什么意思?
        • 最终类不能有子类。在这种情况下,x.getClass() == Class.classx instanceof Class 相同
        • 酷,假设 x 不为空,你更喜欢哪个?
        • 好点。这取决于我是否期望 xnull 我想。 (或者哪个更清楚)
        • 嗯,我刚刚意识到我们也可以使用 java.lang.class.isAssignableFrom,你知道 instanceof 关键字是否在内部使用了这样的函数吗?
        【解决方案11】:

        我基于 jmh-java-benchmark-archetype:2.21 编写了一个性能测试。 JDK是openjdk,版本是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();
        }
        }
        

        【讨论】:

        • 如果我推测,instanceof 的作用可能更复杂。 getClass() == 检查将进行精确的 1:1 检查,其中 instanceof 检查层次结构,即 myHashSet instanceof Collection 会通过,但 myHashSet.getClass() == Collection.class 不会。从本质上讲,它们不是等效的操作,所以我对性能也不同并不感到惊讶。
        【解决方案12】:

        通常,在这种情况下(instanceof 正在检查此基类的子类)不赞成“instanceof”运算符的原因通常是因为您应该做的是将操作移入方法并覆盖它为适当的子类。例如,如果您有:

        if (o instanceof Class1)
           doThis();
        else if (o instanceof Class2)
           doThat();
        //...
        

        您可以将其替换为

        o.doEverything();
        

        然后让“doEverything()”的实现在 Class1 中调用“doThis()”,在 Class2 中调用“doThat()”,以此类推。

        【讨论】:

        • 但有时你不能。如果您正在实现一个包含 Object 的接口,并且您需要知道它是哪种类型,那么 instanceof 确实是唯一的选择。您可以尝试强制转换,但 instanceof 通常更干净。
        【解决方案13】:

        'instanceof'实际上是一个运算符,就像+或-一样,我相信它有自己的JVM字节码指令。它应该很快。

        如果你有一个开关来测试一个对象是否是某个子类的实例,我不应该这样说,那么你的设计可能需要重新设计。考虑将特定于子类的行为下推到子类本身。

        【讨论】:

          【解决方案14】:

          很难说某个 JVM 是如何实现实例的,但在大多数情况下,对象可与结构和类相媲美,并且每个对象结构都有一个指向其实例的类结构的指针。所以实际上 instanceof 为

          if (o instanceof java.lang.String)
          

          可能和下面的 C 代码一样快

          if (objectStruct->iAmInstanceOf == &java_lang_String_class)
          

          假设 JIT 编译器已经到位并且做得不错。

          考虑到这只是访问一个指针,在指针指向的某个偏移量处获取一个指针并将其与另一个指针进行比较(这与测试 32 位数字相等)基本相同,我会说操作实际上可以非常快。

          但不一定非得如此,它在很大程度上取决于 JVM。但是,如果这会成为您代码中的瓶颈操作,我会认为 JVM 实现相当糟糕。即使没有 JIT 编译器并且只解释代码,也应该能够几乎立即进行 instanceof 测试。

          【讨论】:

          • 不需要判断o是否继承自java.lang.String吗?
          • 这就是为什么我说它“可能”一样快。实际上,它执行一个循环,首先检查 iAmInstanceOf 与所讨论的类,然后向上遍历 o 的继承树并对 o 的每个超类重复此检查(因此它可能必须运行此循环几次比赛)
          【解决方案15】:

          德米安和保罗提到了一个好点; 然而,要执行的代码的位置实际上取决于您希望如何使用数据...

          我非常喜欢可以以多种方式使用的小型数据对象。如果您遵循覆盖(多态)方法,则您的对象只能以“一种方式”使用。

          这就是模式的用武之地......

          您可以使用双重分派(如在访问者模式中)要求每个对象“呼叫您”传递自身——这将解析对象的类型。 然而(再次)你需要一个可以用所有可能的子类型“做事”的类。

          我更喜欢使用策略模式,您可以在其中为要处理的每个子类型注册策略。类似于以下内容。请注意,这仅有助于精确的类型匹配,但具有可扩展的优势 - 第三方贡献者可以添加自己的类型和处理程序。 (这对于像 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);
              }
          }
          

          【讨论】:

            【解决方案16】:

            我会就 instanceof 的性能与您联系。但是完全避免问题(或缺乏问题)的一种方法是为您需要对其执行 instanceof 的所有子类创建一个父接口。该接口将是您需要对其进行 instanceof 检查的子类中的 all 方法的超集。如果方法不适用于特定的子类,只需提供此方法的虚拟实现即可。如果我没有误解这个问题,这就是我过去解决问题的方法。

            【讨论】:

              【解决方案17】:

              在现代 Java 版本中,instanceof 运算符作为简单的方法调用更快。这意味着:

              if(a instanceof AnyObject){
              }
              

              更快:

              if(a.getType() == XYZ){
              }
              

              另一件事是如果您需要级联许多 instanceof。那么只调用一次 getType() 的开关会更快。

              【讨论】:

                【解决方案18】:

                InstanceOf 是对糟糕的面向对象设计的警告。

                当前的 JVM 确实意味着 instanceOf 本身并没有太大的性能问题。如果您发现自己经常使用它,尤其是核心功能,那么可能是时候看看设计了。重构为更好的设计所带来的性能(以及简单性/可维护性)收益将大大超过实际 instanceOf 调用所花费的任何实际处理器周期。

                举一个非常简单的编程示例。

                if (SomeObject instanceOf Integer) {
                  [do something]
                }
                if (SomeObject instanceOf Double) {
                  [do something different]
                }
                

                如果架构不佳,更好的选择是让 SomeObject 成为两个子类的父类,其中每个子类都覆盖一个方法 (doSomething),因此代码如下所示:

                Someobject.doSomething();
                

                【讨论】:

                • 我知道这一点。那不是我问的。
                • 不确定是否要投票,因为这是一个好点,但没有回答提出的问题......
                • 我认为代码示例实际上是一个非常糟糕的示例:您不能扩展 Double 类,也不能从其他类派生 Double。如果您在示例中使用了其他类,那就没问题了。
                • 此外,如果 SomeObject 的子类是值对象,那么您不想将逻辑放入其中。例如。 Pie 和 Roast 可能不是 putInOven() 和 putInMouth() 逻辑的正确位置。
                • 自做馅饼和烤肉会很棒
                【解决方案19】:

                如果速度是您的唯一目标,那么使用 int 常量来识别子类似乎可以节省几毫秒的时间

                static final int ID_A = 0;
                static final int ID_B = 1;
                abstract class Base {
                  final int id;
                  Base(int i) { id = i; }
                }
                class A extends Base {
                 A() { super(ID_A); }
                }
                class B extends Base {
                 B() { super(ID_B); }
                }
                ...
                Base obj = ...
                switch(obj.id) {
                case  ID_A: .... break;
                case  ID_B: .... break;
                }
                

                糟糕的 OO 设计,但是如果您的性能分析表明这是您的瓶颈所在,那么可能。在我的代码中,调度代码占用了总执行时间的 10%,这可能有助于提高 1% 的总速度。

                【讨论】:

                  【解决方案20】:

                  如果这确实是您项目中的性能问题,您应该衡量/分析。如果是,我建议重新设计 - 如果可能的话。我很确定您无法击败平台的本机实现(用 C 编写)。在这种情况下,您还应该考虑多重继承。

                  您应该详细说明问题,也许您可​​以使用关联商店,例如如果您只对具体类型感兴趣,则为 Map

                  【讨论】:

                    【解决方案21】:

                    关于 Peter Lawrey 的注释,即最终类不需要 instanceof 并且可以使用引用相等,请小心!即使最终类无法扩展,也不能保证它们被同一个类加载器加载。如果您绝对肯定只有一个类加载器可用于该部分代码,则仅使用 x.getClass() == SomeFinal.class 或其同类。

                    【讨论】:

                    • 如果一个类由不同的类加载器加载,我认为 instanceof 也不会匹配。
                    【解决方案22】:

                    我也更喜欢枚举方法,但我会使用抽象基类来强制子类实现getType() 方法。

                    public abstract class Base
                    {
                      protected enum TYPE
                      {
                        DERIVED_A, DERIVED_B
                      }
                    
                      public abstract TYPE getType();
                    
                      class DerivedA extends Base
                      {
                        @Override
                        public TYPE getType()
                        {
                          return TYPE.DERIVED_A;
                        }
                      }
                    
                      class DerivedB extends Base
                      {
                        @Override
                        public TYPE getType()
                        {
                          return TYPE.DERIVED_B;
                        }
                      }
                    }
                    

                    【讨论】:

                      【解决方案23】:

                      我认为可能值得提交一个反例来反驳此页面上的普遍共识,即“instanceof”并不昂贵,无需担心。我发现我在内部循环中有一些代码(在一些历史性的优化尝试中)确实

                      if (!(seq instanceof SingleItem)) {
                        seq = seq.head();
                      }
                      

                      在 SingleItem 上调用 head() 会返回不变的值。将代码替换为

                      seq = seq.head();
                      

                      让我从 269 毫秒加速到 169 毫秒,尽管在循环中发生了一些相当繁重的事情,比如字符串到双精度的转换。当然,加速可能更多是由于消除了条件分支而不是消除了 instanceof 运算符本身。但我认为值得一提。

                      【讨论】:

                      • 这可能是因为if 本身。如果trues 和falses 的分布接近均匀,推测执行就变得毫无用处,从而导致明显的滞后。
                      【解决方案24】:

                      你专注于错误的事情。 instanceof 与任何其他检查同一事物的方法之间的差异可能甚至无法衡量。如果性能至关重要,那么 Java 可能是错误的语言。主要原因是您无法控制 VM 何时决定要收集垃圾,这可能会使大型程序中的 CPU 达到 100% 几秒钟(MagicDraw 10 非常适合)。除非您能控制运行该程序的每台计算机,否则您无法保证它将在哪个版本的 JVM 上运行,而且许多较旧的 JVM 都存在严重的速度问题。如果它是一个小型应用程序,您可能会使用 Java,但如果您经常读取和丢弃数据,那么您注意到 GC 何时启动。

                      【讨论】:

                      • 对于更现代的 java 垃圾收集算法来说,这比以往任何时候都少。即使是最简单的算法也不再关心你在使用后丢弃了多少内存——它们只关心在年轻代集合中保留了多少。
                      • 很好,除了我在最新的 JVM 上,并且当 GC 运行时我的计算机仍然在爬行。在双核 3GB 内存服务器上。如果性能真的很重要,Java 不是一种可以使用的语言。
                      • @David:当您的应用程序消失一段时间后,您不需要实时出现问题。我遇到的一个有趣的例子是一个连接到 TCP 流的 java 应用程序,该应用程序在 GC 运行时死亡,因为它没有首先关闭流,并且在它返回时无法处理网络流量的过载 - 它会立即进入 GC 运行的循环,当应用程序恢复时,它会尝试处理一堆数据,这使其内存不足,从而触发了 GC 等。Java 非常适合很多任务,但不是非常适合的任务强大的性能是一项要求。
                      • @tloach 听起来像是糟糕的应用程序设计。您谈论“性能”就好像它是一维的。我使用过(并使用过)大量的 Java 应用程序,例如,它们在提供超大数据集的快速交互式统计分析和可视化方面表现出色,或者在非常快速地处理超大事务量方面表现出色。 “性能”不仅仅是一回事,有人可以编写一个管理内存不佳并让 GC 以自己的方式获取的应用程序这一事实并不意味着任何需要“性能”的东西都应该写成其他东西。
                      猜你喜欢
                      • 1970-01-01
                      • 2013-09-06
                      • 1970-01-01
                      • 1970-01-01
                      • 2017-02-15
                      • 2012-12-10
                      • 2010-09-22
                      • 1970-01-01
                      • 2020-11-04
                      相关资源
                      最近更新 更多