为什么JavaAPI在if语句中有看似奇怪的赋值?
我不熟悉编程和Java。我注意到,在JavaAPI中,if语句中有一些方法具有奇怪的赋值 以下是地图界面的一个示例:为什么JavaAPI在if语句中有看似奇怪的赋值?,java,java-8,Java,Java 8,我不熟悉编程和Java。我注意到,在JavaAPI中,if语句中有一些方法具有奇怪的赋值 以下是地图界面的一个示例: default V replace(K key, V value) { V curValue; if (((curValue = get(key)) != null) || containsKey(key)) { curValue = put(key, value); } return curValue; } default V
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
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;
}
这样嵌套作业有什么好处吗?这纯粹是一种风格选择吗?为什么不在第一次声明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接口和其他地方的许多新添加的Java8方法中都注意到了这一点。这种嵌套作业的形式似乎没有必要
编辑:地图界面的另一个示例:
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
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;
}
default V computeIfAbsent(K键,
函数生成的字节码几乎没有差异(一个指令差异):
我写这篇文章是为了生成指令并漂亮地打印它们:
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;
}
}
总之,这是个人偏好。这实际上是复制到局部变量,这会产生更小的字节码,这被视为一种绝对极端的优化方式,在jdk代码的许多其他地方都会看到
另一件事是,多次读取局部变量意味着只读取一次共享变量,例如,如果该变量是一个volatile
,并且您只读取一次并在方法中使用它
编辑
就我所知,这两种方法的区别在于一次阅读
假设我们有以下两种方法:
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;
}
除了:replacessecond
将具有以下内容外,此操作的字节码几乎相同:
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
不算是另一次读取,所以我猜这就是所谓的极端优化?@JacobG。通常在需要变量时而不是在声明变量时初始化变量是一种好的做法。只在需要时声明变量被认为是一种好的做法ded,所以这是一个没有实际意义的点。我看到这个模式编译成dup+存储,而初始化它首先编译成存储+加载。有人想测试JIT是否不同吗?curValue
不会维护get(key)返回的值
。相反,它真正存在的值是put
的返回结果。开发人员似乎想强调这一点。他们似乎强调的是get
仅用于空检查,并且它可能返回的值是冗余的。它们不是“不必要的”,不管是“表面上”还是“其他”。它们只是我n一个与你“表面上”所期望的不同的地方。没有好处,最好马上完成这个任务。但有时人们更喜欢炫耀而不是编写干净易读的代码。嗨@Eugene!我正要回答关于局部性的问题,但你先写了这个。也许你想扩展关于局部性的内容,为什么它很好d(我相信它的主要优点是它在指令级产生更多的缓存命中,但我可能在这里错了,您需要确认一下).我认为具有良好局部性的方法内部的代码更容易预测,我的意思是编译器可以预测哪些部分需要积极优化,但我也不确定这方面的任何内容。我只是想给你一些从我记忆深处产生的背景…忽略我之前的评论。局部性是保持不变的声明CurveValue时调用了now和if get(键)。你说的“复制到局部变量”是什么意思?if语句之外的赋值是做什么的?@Ivan在if语句之外?我不明白,你没有显示整个代码吗?就复制到局部变量而言,你只使用局部变量curValue
而不是shared
variablemap.get(..)
;这是多线程代码btw中的常见模式,在此模式下,您将读取一次并对已读取的值进行操作,然后在stack@Ivan这里可能有更好的方式来表达同样的话(我今天早上可能会很慢)…性能并不是这里唯一的因素。为什么有人会喜欢这种只出现在某些区域的嵌套分配方式?有些人可能会认为这是为了性能,你说的是性能似乎没有明显的提高。这些方法是由同一个开发人员提出的,所以问题仍然存在:这是怎么回事e我们还没有注意到设计/可读性问题,或者可能是一些您尚未发现测试的深层次优化。可能需要分析JITed输出,以真正确认您的“个人偏好”要求。(续)更不用说,一些开发人员知道未来的更新,他们编写代码为下一次更新做准备。一些Java 8功能被推迟,因此这可能是Java 9中的优势。如果您想完全揭穿与性能的关系,请尝试Java 9并上传JIT日志(甚至只使用JITWatch)。如果你不想,我会理解,对于可能相同的答案,这将是多余的工作。我只是觉得,只分析一个版本的字节码并不能真正解释这个谜团。@VinceEmigh;你认为在外部和内部分配一个变量会导致深层次的优化,而这不是在说明中发现的,而是在JIT中,它是一个赋值。在函数的末尾,它返回结果。这就是它。没有别的东西了。我们甚至有其他语言,它是一种C和C++固有的风格。有一种情况会被优化出来,那就是短手评价。例如:<代码>(条件)(err=someFunc())==null))
。如果条件为真,someVar
将被取消