我是编程和Java的新手。我注意到,在Java API中,if语句中有一些奇怪的赋值方法。
以下是Map界面的示例:
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
以这种方式嵌套作业是否有某种好处?这纯粹是一种风格选择吗?为什么不在首次声明curValue
时进行分配?
// 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;
}
我在Map界面和其他地方的许多新添加的Java 8方法中都注意到了这一点。这种嵌套作业的形式似乎是不必要的。
编辑:Map界面的另一个例子:
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;
}
答案 0 :(得分:3)
这样做实际上是将复制到本地变量,这会生成更小的字节代码,并且它被视为绝对极端优化方式,你将在jdk代码中的许多其他地方看到这个。
另一个原因是多次读取局部变量,意味着只读取一次共享变量,例如,如果它只是一个volatile
而你只读一次并在方法中使用它。
修改强>
这两种方法之间的差异是单次读取 AS FAR AS I TOT
假设我们有这两种方法:
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;
}
这个字节代码几乎相同,除了:replaceSecond
将会:
astore_3 // V curValue = map.get(key); store to curValue
aload_3 // curValue != null; read the value from curValue
虽然replace
方法将是:
dup // duplicate whatever value came from map.get(key)
astore_3 // store the value, thus "consuming" it form the stack
根据我的理解,dup
不算再读一遍,所以我猜这就是所谓的极端优化?
答案 1 :(得分:2)
生成的字节码几乎没有区别(一个指令差异):https://www.diffchecker.com/okjPcBIb
我写了这个来生成说明并打印出来:
package acid;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.tree.ClassNode;
import jdk.internal.org.objectweb.asm.tree.InsnList;
import jdk.internal.org.objectweb.asm.util.Printer;
import jdk.internal.org.objectweb.asm.util.Textifier;
import jdk.internal.org.objectweb.asm.util.TraceMethodVisitor;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
public class Acid {
public interface Map<K,V> {
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
boolean containsKey(Object key);
V get(Object key);
V put(K key, V value);
}
public void print() {
try {
ClassNode node = loadRelativeClassNode(Map.class.getName());
node.methods.stream().filter(m -> m.name.equals("replace")).forEach(m -> {
System.out.println("\n\nMethod: " + m.name + "" + m.desc + "\n");
System.out.println("-------------------------------\n");
Printer printer = new Textifier();
TraceMethodVisitor visitor = new TraceMethodVisitor(printer);
Arrays.stream(m.instructions.toArray()).forEachOrdered(instruction -> {
instruction.accept(visitor);
StringWriter writer = new StringWriter();
printer.print(new PrintWriter(writer));
printer.getText().clear();
System.out.print(writer.toString());
});
});
} catch (Exception e) {
e.printStackTrace();
}
}
//Usage: `loadJVMClassNode("java.util.Map")`
private static ClassNode loadJVMClassNode(String cls) throws IOException, ClassNotFoundException {
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class clz = loader.loadClass(cls);
InputStream url = clz.getResourceAsStream(clz.getSimpleName() + ".class");
ClassNode node = new ClassNode();
ClassReader reader = new ClassReader(url);
reader.accept(node, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
return node;
}
//Usage: `loadJVMClassNode(Acid.Map.class.getName())`
private static ClassNode loadRelativeClassNode(String cls) throws IOException, ClassNotFoundException {
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class clz = loader.loadClass(cls);
InputStream url = clz.getResourceAsStream(("./" + clz.getName() + ".class").replace(clz.getPackage().getName() + ".", ""));
ClassNode node = new ClassNode();
ClassReader reader = new ClassReader(url);
reader.accept(node, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
return node;
}
}
用法:new Acid().print();
输出差异是单个DUP
指令与ALOAD
指令..
对于那些说..那么你的界面不是Java JDK的界面..我也做了一个差异:https://www.diffchecker.com/zBVTu7jK。
我非常有信心JIT会将它们视为完全相同的代码,无论您是在if语句之外还是在其中初始化变量。
上面的所有代码都在运行:
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
OSX High-Sierra 10.13.3
IDE: Intelli-J
总而言之,这是个人喜好..