Issue
Suppose I have an object A
which can call getB()
call getC()
call getD()
call doSomething…
Now I want to use some methods of the D
multiple times in my application i.e.
A.getB().getC().getD().doSomething1();
A.getB().getC().getD().doSomething2();
A.getB().getC().getD().doSomething3();
I know it's best to create a variable d = A.getB().getC().getD()
and then use it. But I wonder if there's any performance issue with chaining multiple methods like this.
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(){};
}
Solution
Although you mentioned that you already know it: You should avoid such long method chains, for several reasons:
- It's an indication of bad OO design (more precisely, it looks like a violation of the Law Of Demeter)
- It's error-prone. If a line like
a.getB().getC().getD().doSomething()
throws aNullPointerException
, you'll have no fun with debugging this... - Let me be a bit subjective here: It looks horrible.
Of course, you have to consider what the methods are doing actually. Although a get
-method should usually just return a value, you don't know whether it really does this. Even something like
List<T> getList() {
// Return an unmodifiable view to the caller
return Collections.unmodifiableList(internalList);
}
(which certainly is a good practice) may change the outcome.
That being said, and considering that these get
-methods are really only plain, stupid Getters:
It does not have an impact on performance in practice. The method calls will be inlined by the JIT.
As an example, consider the following program:
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<n; i++)
{
a.getB().getC().getD().doSomething1();
a.getB().getC().getD().doSomething2();
a.getB().getC().getD().doSomething3();
}
}
private static void runUnChained(ChainA a, int n)
{
ChainD d = a.getB().getC().getD();
for (int i=0; i<n; i++)
{
d.doSomething1();
d.doSomething2();
d.doSomething3();
}
}
}
It performs the calls like you described them, once as a chained call, and once as an unchained version.
Running it with
java -server -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly Chaining
on a HotSpot-Disassembler-enabled JVM results in the following output (no need to read it, just for reference)
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
By comparing the outputs, it can be seen that they are essentially equal. The calls have been inlined in both cases.
One could now argue that the doSomething
methods are so trivial that they have been inlined as well, and the results may be different for more complex doSomething
methods. This may be true. But a quick test with a method like
int doSomething(int i)
{
List<Integer> list = new ArrayList<Integer>(
Arrays.asList(1,2,3,4,5,6,7,8,9,10));
Collections.sort(list);
return list.get(i);
}
shows that the actual inlining of the chained calls still happen, and when the "inner" methods become even more complex, any potential overhead that might occur due to the chained calls will become negligible compared to what is done in the doSomething
method.
Answered By - Marco13
Answer Checked By - Marie Seifert (JavaFixing Admin)