Local variables are held on the stack and are very fast, faster than reading a field, even before the JVM optimizes the method (if it bothers to, the method may not be called enough in a given program). In the common case, values
is not null
, so it's retrieved once, then the local is tested and used as the return value. If you didn't use a local variable, you'd have to read the field twice (once for the null
check, once for the return
). That isn't as fast as reusing the local.
There may also be a general style rule (related to efficiency) being applied that doesn't necessarily show its benefits in values()
, specifically.
Let's look at it in more depth. Consider these two implementations of a values
-like method, one using a local and the other using the field everywhere:
private transient Map<T, U> map1 = null;
public Map<T, U> likeValues() {
Map<T, U> m = this.map1;
if (m == null) {
m = new HashMap<T, U>();
this.map1 = m;
}
return m;
}
private transient Map<T, U> map2 = null;
public Map<T, U> usingField() {
if (this.map2 == null) {
this.map2 = new HashMap<T, U>();
}
return this.map2;
}
Let's look at the bytecode for them, I've added comments (they may not be perfect, I'm not a bytecode expert and moreover I'm going for clarity rather than describing each and every stack operation in detail):
public java.util.Map likeValues();
Code:
0: aload_0 // Load `this` onto the stack
1: getfield #2 // Get the field's value
4: astore_1 // Store it in local variable 1
5: aload_1 // Load local variable 1
6: ifnonnull 22 // Jump if not null to #22
9: new #5 // Create the HashMap
12: dup // Duplicate the top value on the stack
13: invokespecial #6 // Call method java/util/HashMap."":()V to init the HashMap
16: astore_1 // Store the result in local variable 1
17: aload_0 // Load `this` onto the stack
18: aload_1 // Load local variable 1 onto the stack
19: putfield #2 // Store the value in the field
22: aload_1 // Load local variable 1 onto the stack
23: areturn // Return it
public java.util.Map usingField();
Code:
0: aload_0 // Load `this` onto the stack
1: getfield #3 // Get the field's value
4: ifnonnull 18 // Jump if not null to #18
7: aload_0 // Load `this` onto the stack
8: new #5 // Create the HashMap
11: dup // Duplicate the top value on the stack
12: invokespecial #6 // Call method java/util/HashMap."":()V to init the HashMap
15: putfield #3 // Store it in the field
18: aload_0 // Load `this` onto the stack
19: getfield #3 // Get the field's value
22: areturn // Return it
Let's look only at the non-null
path in those:
public java.util.Map likeValues();
Code:
0: aload_0 // Load `this` onto the stack
1: getfield #2 // Get the field's value
4: astore_1 // Store it in local variable 1
5: aload_1 // Load local variable 1
6: ifnonnull 22 // Jump if not null to #22
// ...skipped...
22: aload_1 // Load local variable 1 onto the stack
23: areturn // Return it
public java.util.Map usingField();
Code:
0: aload_0 // Load `this` onto the stack
1: getfield #3 // Get the field's value
4: ifnonnull 18 // Jump if not null to #18
// ...skipped...
18: aload_0 // Load `this` onto the stack
19: getfield #3 // Get the field's value
22: areturn // Return it
In likeValues
, the field is only read once, and then the local variable (on the stack) is used for the comparison and return.
In usingField
, the field is read twice. Reading a field is not as fast as using a stack value.