Java中的Chaining方法是否缓慢?

假设我有一个对象A可以调用getB()调用getC()调用getD()调用doSomething …现在我想在我的应用程序中使用D多次的一些方法,即

A.getB().getC().getD().doSomething1(); A.getB().getC().getD().doSomething2(); A.getB().getC().getD().doSomething3(); 

。 我知道最好创建一个变量d = A.getB().getC().getD()然后使用它。 但是我想知道链接这样的多种方法是否有任何性能问题。

 class A { private B b; B getB() { return b; } } class B { private C c; C getC() { return c; } } class C { private D d; D getD() { return d; } } class D { void doSomething1(){}; void doSomething2(){}; void doSomething3(){}; } 

虽然你提到你已经知道它了:你应该避免这么长的方法链,原因有以下几点:

  • 这表明OO设计不好(更确切地说,它看起来违反了得墨忒耳定律 )
  • 这很容易出错。 如果一行像a.getB().getC().getD().doSomething()抛出一个NullPointerException ,调试这个就没有乐趣…
  • 让我在这里有点主观:它看起来很可怕。

当然,您必须考虑这些方法实际上在做什么。 虽然get -method通常只返回一个值,但你不知道它是否真的这样做了。 甚至像

 List getList() { // Return an unmodifiable view to the caller return Collections.unmodifiableList(internalList); } 

(这当然一种很好的做法)可能会改变结果。

话虽这么说,并考虑到这些get -methods真的只是普通的,愚蠢的Getters

它在实践中对性能没有影响。 方法调用将由JIT内联。

例如,请考虑以下程序:

 class ChainA { private ChainB b = new ChainB(); ChainB getB() { return b; } } class ChainB { private ChainC c = new ChainC(); ChainC getC() { return c; } } class ChainC { private ChainD d = new ChainD(); ChainD getD() { return d; } } class ChainD { private int result = 0; int getResult() { return result; } void doSomething1(){ result += 1; } void doSomething2(){ result += 2; } void doSomething3(){ result += 3; } } class Chaining { public static void main(String args[]) { for (int n=100; n<10000; n+=100) { ChainA a0 = new ChainA(); runChained(a0, n); System.out.println(a0.getB().getC().getD().getResult()); ChainA a1 = new ChainA(); runUnChained(a1, n); System.out.println(a1.getB().getC().getD().getResult()); } } private static void runChained(ChainA a, int n) { for (int i=0; i 

它会像你描述的那样调用它们,一次作为链式调用,一次作为一个非链接版本。

运行它

java -server -XX:+ UnlockDiagnosticVMOptions -XX:+ TraceClassLoading -XX:+ LogCompilation -XX:+ PrintAssembly Chaining

在支持HotSpot-Disassembler的JVM上产生以下输出(无需读取 ,仅供参考)

runChained:

 Decoding compiled method 0x0000000002885d50: Code: [Entry Point] [Verified Entry Point] [Constants] # {method} {0x0000000055360550} 'runChained' '(LChainA;I)V' in 'Chaining' # parm0: rdx:rdx = 'ChainA' # parm1: r8 = int # [sp+0x30] (sp of caller) 0x0000000002885e80: mov %eax,-0x6000(%rsp) 0x0000000002885e87: push %rbp 0x0000000002885e88: sub $0x20,%rsp ;*synchronization entry ; - Chaining::runChained@-1 (line 47) 0x0000000002885e8c: mov %rdx,%r9 0x0000000002885e8f: mov %r8d,%ebx 0x0000000002885e92: test %r8d,%r8d 0x0000000002885e95: jle 0x0000000002885f8a ;*if_icmpge ; - Chaining::runChained@4 (line 47) 0x0000000002885e9b: mov 0xc(%rdx),%r11d ;*getfield b ; - ChainA::getB@1 (line 4) ; - Chaining::runChained@8 (line 49) ; implicit exception: dispatches to 0x0000000002885f96 0x0000000002885e9f: mov 0xc(%r11),%r10d ;*getfield c ; - ChainB::getC@1 (line 10) ; - Chaining::runChained@11 (line 49) ; implicit exception: dispatches to 0x0000000002885f96 0x0000000002885ea3: mov 0xc(%r10),%edx ;*getfield d ; - ChainC::getD@1 (line 16) ; - Chaining::runChained@14 (line 49) ; implicit exception: dispatches to 0x0000000002885f96 0x0000000002885ea7: mov 0xc(%rdx),%r11d ;*getfield result ; - ChainD::doSomething1@2 (line 23) ; - Chaining::runChained@17 (line 49) ; implicit exception: dispatches to 0x0000000002885f96 0x0000000002885eab: xor %r10d,%r10d 0x0000000002885eae: xor %esi,%esi 0x0000000002885eb0: xor %r8d,%r8d 0x0000000002885eb3: xor %ecx,%ecx ;*aload_0 ; - Chaining::runChained@7 (line 49) 0x0000000002885eb5: add %r11d,%esi ;*getfield result ; - ChainD::doSomething1@2 (line 23) ; - Chaining::runChained@17 (line 49) 0x0000000002885eb8: add %ecx,%r8d 0x0000000002885ebb: mov %esi,%eax 0x0000000002885ebd: add $0x6,%eax ;*iadd ; - ChainD::doSomething3@6 (line 25) ; - Chaining::runChained@43 (line 51) 0x0000000002885ec0: mov %eax,0xc(%rdx) ;*putfield result ; - ChainD::doSomething3@7 (line 25) ; - Chaining::runChained@43 (line 51) 0x0000000002885ec3: mov %r10d,%edi 0x0000000002885ec6: inc %edi ;*iinc ; - Chaining::runChained@46 (line 47) 0x0000000002885ec8: cmp $0x1,%edi 0x0000000002885ecb: jge 0x0000000002885eee ;*if_icmpge ; - Chaining::runChained@4 (line 47) 0x0000000002885ecd: mov %r10d,%ecx 0x0000000002885ed0: shl %ecx 0x0000000002885ed2: mov %r8d,%esi 0x0000000002885ed5: add $0x6,%esi 0x0000000002885ed8: mov %ecx,%r8d 0x0000000002885edb: add $0x2,%r8d 0x0000000002885edf: shl $0x2,%r10d 0x0000000002885ee3: mov %r10d,%ecx 0x0000000002885ee6: add $0x4,%ecx 0x0000000002885ee9: mov %edi,%r10d 0x0000000002885eec: jmp 0x0000000002885eb5 0x0000000002885eee: mov %ebx,%r11d 0x0000000002885ef1: add $0xfffffff1,%r11d 0x0000000002885ef5: mov $0x80000000,%r9d 0x0000000002885efb: cmp %r11d,%ebx 0x0000000002885efe: cmovl %r9d,%r11d 0x0000000002885f02: cmp %r11d,%edi 0x0000000002885f05: jge 0x0000000002885f3c 0x0000000002885f07: sub %r8d,%esi 0x0000000002885f0a: nopw 0x0(%rax,%rax,1) ;*aload_0 ; - Chaining::runChained@7 (line 49) 0x0000000002885f10: mov %edi,%r8d 0x0000000002885f13: shl %r8d 0x0000000002885f16: mov %edi,%r9d 0x0000000002885f19: shl $0x2,%r9d 0x0000000002885f1d: add %r9d,%r8d 0x0000000002885f20: add %esi,%r8d ;*getfield result ; - ChainD::doSomething1@2 (line 23) ; - Chaining::runChained@17 (line 49) 0x0000000002885f23: mov %r8d,%eax 0x0000000002885f26: add $0x60,%eax ;*iadd ; - ChainD::doSomething3@6 (line 25) ; - Chaining::runChained@43 (line 51) 0x0000000002885f29: add $0x5a,%r8d 0x0000000002885f2d: mov %r8d,0xc(%rdx) 0x0000000002885f31: mov %eax,0xc(%rdx) ;*putfield result ; - ChainD::doSomething3@7 (line 25) ; - Chaining::runChained@43 (line 51) 0x0000000002885f34: add $0x10,%edi ;*iinc ; - Chaining::runChained@46 (line 47) 0x0000000002885f37: cmp %r11d,%edi 0x0000000002885f3a: jl 0x0000000002885f10 ;*getfield result ; - ChainD::doSomething1@2 (line 23) ; - Chaining::runChained@17 (line 49) 0x0000000002885f3c: cmp %ebx,%edi 0x0000000002885f3e: jge 0x0000000002885f8a 0x0000000002885f40: mov %edi,%r10d 0x0000000002885f43: shl $0x2,%r10d 0x0000000002885f47: mov %edi,%r11d 0x0000000002885f4a: shl %r11d 0x0000000002885f4d: mov %r11d,%r8d 0x0000000002885f50: add %r10d,%r8d 0x0000000002885f53: sub %r8d,%eax 0x0000000002885f56: xchg %ax,%ax ;*aload_0 ; - Chaining::runChained@7 (line 49) 0x0000000002885f58: add %r11d,%r10d 0x0000000002885f5b: add %eax,%r10d 0x0000000002885f5e: add $0x6,%r10d 0x0000000002885f62: mov %r10d,0xc(%rdx) ;*putfield result ; - ChainD::doSomething3@7 (line 25) ; - Chaining::runChained@43 (line 51) 0x0000000002885f66: mov %edi,%r8d 0x0000000002885f69: inc %r8d ;*iinc ; - Chaining::runChained@46 (line 47) 0x0000000002885f6c: cmp %ebx,%r8d 0x0000000002885f6f: jge 0x0000000002885f8a 0x0000000002885f71: mov %edi,%r10d 0x0000000002885f74: shl $0x2,%r10d 0x0000000002885f78: shl %edi 0x0000000002885f7a: add $0x4,%r10d 0x0000000002885f7e: mov %edi,%r11d 0x0000000002885f81: add $0x2,%r11d 0x0000000002885f85: mov %r8d,%edi 0x0000000002885f88: jmp 0x0000000002885f58 ;*if_icmpge ; - Chaining::runChained@4 (line 47) 0x0000000002885f8a: add $0x20,%rsp 0x0000000002885f8e: pop %rbp 0x0000000002885f8f: test %eax,-0x26c5f95(%rip) # 0x00000000001c0000 ; {poll_return} 0x0000000002885f95: retq 0x0000000002885f96: mov $0xffffff86,%edx 0x0000000002885f9b: mov %r9,%rbp 0x0000000002885f9e: mov %r8d,(%rsp) 0x0000000002885fa2: nop 0x0000000002885fa3: callq 0x00000000027b7320 ; OopMap{rbp=Oop off=296} ;*aload_0 ; - Chaining::runChained@7 (line 49) ; {runtime_call} 0x0000000002885fa8: int3 ;*aload_0 ; - Chaining::runChained@7 (line 49) 0x0000000002885fa9: hlt 0x0000000002885faa: hlt 0x0000000002885fab: hlt 0x0000000002885fac: hlt 0x0000000002885fad: hlt 0x0000000002885fae: hlt 0x0000000002885faf: hlt 0x0000000002885fb0: hlt 0x0000000002885fb1: hlt 0x0000000002885fb2: hlt 0x0000000002885fb3: hlt 0x0000000002885fb4: hlt 0x0000000002885fb5: hlt 0x0000000002885fb6: hlt 0x0000000002885fb7: hlt 0x0000000002885fb8: hlt 0x0000000002885fb9: hlt 0x0000000002885fba: hlt 0x0000000002885fbb: hlt 0x0000000002885fbc: hlt 0x0000000002885fbd: hlt 0x0000000002885fbe: hlt 0x0000000002885fbf: hlt [Exception Handler] [Stub Code] 0x0000000002885fc0: jmpq 0x00000000028694a0 ; {no_reloc} [Deopt Handler Code] 0x0000000002885fc5: callq 0x0000000002885fca 0x0000000002885fca: subq $0x5,(%rsp) 0x0000000002885fcf: jmpq 0x00000000027b6f40 ; {runtime_call} 0x0000000002885fd4: hlt 0x0000000002885fd5: hlt 0x0000000002885fd6: hlt 0x0000000002885fd7: hlt 

runUnChained:

 Decoding compiled method 0x00000000028893d0: Code: [Entry Point] [Verified Entry Point] [Constants] # {method} {0x0000000055360628} 'runUnChained' '(LChainA;I)V' in 'Chaining' # parm0: rdx:rdx = 'ChainA' # parm1: r8 = int # [sp+0x30] (sp of caller) 0x0000000002889500: mov %eax,-0x6000(%rsp) 0x0000000002889507: push %rbp 0x0000000002889508: sub $0x20,%rsp ;*synchronization entry ; - Chaining::runUnChained@-1 (line 57) 0x000000000288950c: mov 0xc(%rdx),%r11d ;*getfield b ; - ChainA::getB@1 (line 4) ; - Chaining::runUnChained@1 (line 57) ; implicit exception: dispatches to 0x0000000002889612 0x0000000002889510: mov 0xc(%r11),%r10d ;*getfield c ; - ChainB::getC@1 (line 10) ; - Chaining::runUnChained@4 (line 57) ; implicit exception: dispatches to 0x000000000288961d 0x0000000002889514: mov 0xc(%r10),%ebx ;*getfield d ; - ChainC::getD@1 (line 16) ; - Chaining::runUnChained@7 (line 57) ; implicit exception: dispatches to 0x0000000002889629 0x0000000002889518: mov %r8d,%esi 0x000000000288951b: test %r8d,%r8d 0x000000000288951e: jle 0x0000000002889606 ;*if_icmpge ; - Chaining::runUnChained@15 (line 58) 0x0000000002889524: mov 0xc(%rbx),%r10d ;*getfield result ; - ChainD::doSomething1@2 (line 23) ; - Chaining::runUnChained@19 (line 60) ; implicit exception: dispatches to 0x0000000002889635 0x0000000002889528: xor %r8d,%r8d 0x000000000288952b: xor %edx,%edx 0x000000000288952d: xor %r9d,%r9d 0x0000000002889530: xor %r11d,%r11d ;*aload_2 ; - Chaining::runUnChained@18 (line 60) 0x0000000002889533: add %r10d,%edx ;*getfield result ; - ChainD::doSomething1@2 (line 23) ; - Chaining::runUnChained@19 (line 60) 0x0000000002889536: add %r11d,%r9d 0x0000000002889539: mov %edx,%edi 0x000000000288953b: add $0x6,%edi ;*iadd ; - ChainD::doSomething3@6 (line 25) ; - Chaining::runUnChained@27 (line 62) 0x000000000288953e: mov %edi,0xc(%rbx) ;*putfield result ; - ChainD::doSomething3@7 (line 25) ; - Chaining::runUnChained@27 (line 62) 0x0000000002889541: mov %r8d,%ecx 0x0000000002889544: inc %ecx ;*iinc ; - Chaining::runUnChained@30 (line 58) 0x0000000002889546: cmp $0x1,%ecx 0x0000000002889549: jge 0x000000000288956e ;*if_icmpge ; - Chaining::runUnChained@15 (line 58) 0x000000000288954b: mov %r8d,%r11d 0x000000000288954e: shl %r11d 0x0000000002889551: mov %r9d,%edx 0x0000000002889554: add $0x6,%edx 0x0000000002889557: mov %r11d,%r9d 0x000000000288955a: add $0x2,%r9d 0x000000000288955e: shl $0x2,%r8d 0x0000000002889562: mov %r8d,%r11d 0x0000000002889565: add $0x4,%r11d 0x0000000002889569: mov %ecx,%r8d 0x000000000288956c: jmp 0x0000000002889533 0x000000000288956e: mov %esi,%r10d 0x0000000002889571: add $0xfffffff1,%r10d 0x0000000002889575: mov $0x80000000,%r11d 0x000000000288957b: cmp %r10d,%esi 0x000000000288957e: cmovl %r11d,%r10d 0x0000000002889582: cmp %r10d,%ecx 0x0000000002889585: jge 0x00000000028895bc 0x0000000002889587: sub %r9d,%edx 0x000000000288958a: nopw 0x0(%rax,%rax,1) ;*aload_2 ; - Chaining::runUnChained@18 (line 60) 0x0000000002889590: mov %ecx,%r11d 0x0000000002889593: shl %r11d 0x0000000002889596: mov %ecx,%r8d 0x0000000002889599: shl $0x2,%r8d 0x000000000288959d: add %r8d,%r11d 0x00000000028895a0: add %edx,%r11d ;*getfield result ; - ChainD::doSomething1@2 (line 23) ; - Chaining::runUnChained@19 (line 60) 0x00000000028895a3: mov %r11d,%edi 0x00000000028895a6: add $0x60,%edi ;*iadd ; - ChainD::doSomething3@6 (line 25) ; - Chaining::runUnChained@27 (line 62) 0x00000000028895a9: add $0x5a,%r11d 0x00000000028895ad: mov %r11d,0xc(%rbx) 0x00000000028895b1: mov %edi,0xc(%rbx) ;*putfield result ; - ChainD::doSomething3@7 (line 25) ; - Chaining::runUnChained@27 (line 62) 0x00000000028895b4: add $0x10,%ecx ;*iinc ; - Chaining::runUnChained@30 (line 58) 0x00000000028895b7: cmp %r10d,%ecx 0x00000000028895ba: jl 0x0000000002889590 ;*getfield result ; - ChainD::doSomething1@2 (line 23) ; - Chaining::runUnChained@19 (line 60) 0x00000000028895bc: cmp %esi,%ecx 0x00000000028895be: jge 0x0000000002889606 0x00000000028895c0: mov %ecx,%r11d 0x00000000028895c3: shl $0x2,%r11d 0x00000000028895c7: mov %ecx,%r8d 0x00000000028895ca: shl %r8d 0x00000000028895cd: mov %r8d,%r9d 0x00000000028895d0: add %r11d,%r9d 0x00000000028895d3: sub %r9d,%edi 0x00000000028895d6: xchg %ax,%ax ;*aload_2 ; - Chaining::runUnChained@18 (line 60) 0x00000000028895d8: add %r8d,%r11d 0x00000000028895db: add %edi,%r11d 0x00000000028895de: add $0x6,%r11d 0x00000000028895e2: mov %r11d,0xc(%rbx) ;*putfield result ; - ChainD::doSomething3@7 (line 25) ; - Chaining::runUnChained@27 (line 62) 0x00000000028895e6: mov %ecx,%edx 0x00000000028895e8: inc %edx ;*iinc ; - Chaining::runUnChained@30 (line 58) 0x00000000028895ea: cmp %esi,%edx 0x00000000028895ec: jge 0x0000000002889606 0x00000000028895ee: mov %ecx,%r11d 0x00000000028895f1: shl $0x2,%r11d 0x00000000028895f5: shl %ecx 0x00000000028895f7: add $0x4,%r11d 0x00000000028895fb: mov %ecx,%r8d 0x00000000028895fe: add $0x2,%r8d 0x0000000002889602: mov %edx,%ecx 0x0000000002889604: jmp 0x00000000028895d8 ;*if_icmpge ; - Chaining::runUnChained@15 (line 58) 0x0000000002889606: add $0x20,%rsp 0x000000000288960a: pop %rbp 0x000000000288960b: test %eax,-0x26c9611(%rip) # 0x00000000001c0000 ; {poll_return} 0x0000000002889611: retq 0x0000000002889612: mov $0xfffffff6,%edx 0x0000000002889617: callq 0x00000000027b7320 ; OopMap{off=284} ;*invokevirtual getB ; - Chaining::runUnChained@1 (line 57) ; {runtime_call} 0x000000000288961c: int3 ;*invokevirtual getB ; - Chaining::runUnChained@1 (line 57) 0x000000000288961d: mov $0xfffffff6,%edx 0x0000000002889622: nop 0x0000000002889623: callq 0x00000000027b7320 ; OopMap{off=296} ;*invokevirtual getC ; - Chaining::runUnChained@4 (line 57) ; {runtime_call} 0x0000000002889628: int3 ;*invokevirtual getC ; - Chaining::runUnChained@4 (line 57) 0x0000000002889629: mov $0xfffffff6,%edx 0x000000000288962e: nop 0x000000000288962f: callq 0x00000000027b7320 ; OopMap{off=308} ;*invokevirtual getD ; - Chaining::runUnChained@7 (line 57) ; {runtime_call} 0x0000000002889634: int3 ;*invokevirtual getD ; - Chaining::runUnChained@7 (line 57) 0x0000000002889635: mov $0xffffff86,%edx 0x000000000288963a: mov %ebx,%ebp 0x000000000288963c: mov %r8d,(%rsp) 0x0000000002889640: data32 xchg %ax,%ax 0x0000000002889643: callq 0x00000000027b7320 ; OopMap{rbp=NarrowOop off=328} ;*aload_2 ; - Chaining::runUnChained@18 (line 60) ; {runtime_call} 0x0000000002889648: int3 ;*aload_2 ; - Chaining::runUnChained@18 (line 60) 0x0000000002889649: hlt 0x000000000288964a: hlt 0x000000000288964b: hlt 0x000000000288964c: hlt 0x000000000288964d: hlt 0x000000000288964e: hlt 0x000000000288964f: hlt 0x0000000002889650: hlt 0x0000000002889651: hlt 0x0000000002889652: hlt 0x0000000002889653: hlt 0x0000000002889654: hlt 0x0000000002889655: hlt 0x0000000002889656: hlt 0x0000000002889657: hlt 0x0000000002889658: hlt 0x0000000002889659: hlt 0x000000000288965a: hlt 0x000000000288965b: hlt 0x000000000288965c: hlt 0x000000000288965d: hlt 0x000000000288965e: hlt 0x000000000288965f: hlt [Exception Handler] [Stub Code] 0x0000000002889660: jmpq 0x00000000028694a0 ; {no_reloc} [Deopt Handler Code] 0x0000000002889665: callq 0x000000000288966a 0x000000000288966a: subq $0x5,(%rsp) 0x000000000288966f: jmpq 0x00000000027b6f40 ; {runtime_call} 0x0000000002889674: hlt 0x0000000002889675: hlt 0x0000000002889676: hlt 0x0000000002889677: hlt 

通过比较输出,可以看出它们基本相同。 两种情况都内联了这些电话。

人们现在可以争辩说doSomething方法是如此微不足道,以至于它们也被内联,并且对于更复杂的doSomething方法,结果可能会有所不同。 这可能是真的。 但用类似的方法快速测试

 int doSomething(int i) { List list = new ArrayList( Arrays.asList(1,2,3,4,5,6,7,8,9,10)); Collections.sort(list); return list.get(i); } 

表明链接调用的实际内联仍然发生,并且当“内部”方法变得更加复杂时,由于链接调用而可能发生的任何潜在开销与doSomething方法中所做的相比将变得可以忽略不计。

如果对象被实例化一次,则不会出现任何性能问题。 当你调用getB()。getC()…你只需要在对象上获得非常快速和简单操作的链接。 但实际上,这样的代码看起来并不是很好。

什么是“慢”

显然比读取局部变量的值慢,但除此之外,方法调用非常快,你不必担心。

另一方面,链接像A.getB().getC().getD().doSomething1()为重构而尖叫。 至少在A类中添加一个返回D的新方法或直接调用doSomething1()

每个方法调用,操作,赋值或大多数其他方法都是针对处理器的。 您提供的示例可能运行得非常快; 但是针对处理器的每个滴答都会开始减慢速度。 游戏开发者非常关注这一点,因为他们需要能够保持帧速率; 但是一般和商业应用程序并没有那么关心。

关键点:链接将开始影响性能,如果有很多或链式方法必须做很多工作。