I'm new to programming and Java. I've noticed that, in the Java API, there are methods with strange assignments inside if statements.
Here is an example from the Map interface:
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
Is there some sort of benefit to nesting the assignment this way? Is this purely a style choice? Why not just do the assignment when curValue
is first declared?
// why not do it like this?
default V replace(K key, V value) {
V curValue = get(key); // not nested
if (curValue != null || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
I've noticed this in a lot of the newly added Java 8 methods in the Map interface and elsewhere. This form of nesting the assignment seems unnecessary.
Edit: another example from the Map interface:
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
What this is doing is actually copying to a local variable, this is producing smaller byte code, and it is seen as an absolute extreme way of optimization, you will see this in numerous other places in the jdk code.
One other thing is that reading a local variable multiple times, implies reading a shared variable only once, if that for example would have been a volatile
and you would read it only once and work with it within the method.
EDIT
The difference between the two approaches is a single read AS FAR AS I CAN TELL
Suppose we have these two methods:
V replace(K key, V value) {
V curValue;
if ((curValue = map.get(key)) != null || map.containsKey(key)) {
curValue = map.put(key, value);
}
return curValue;
}
V replaceSecond(K key, V value) {
V curValue = map.get(key); // write
if (curValue != null || map.containsKey(key)) { // read
curValue = map.put(key, value); // write
}
return curValue;
}
The byte code for this is almost identical, except for: replaceSecond
is going to have:
astore_3 // V curValue = map.get(key); store to curValue
aload_3 // curValue != null; read the value from curValue
While the replace
method is going to be:
dup // duplicate whatever value came from map.get(key)
astore_3 // store the value, thus "consuming" it form the stack
In my understanding, dup
does not count as yet another read, so I guess this is what is referred as an extreme optimization?