首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么Java在if语句中有看似奇怪的赋值?

为什么Java在if语句中有看似奇怪的赋值?
EN

Stack Overflow用户
提问于 2018-02-07 00:21:49
回答 2查看 307关注 0票数 10

我对编程和Java很陌生。我注意到,在Java中,在if语句中有一些带有奇怪赋值的方法。

下面是Map接口中的一个示例:

代码语言:javascript
复制
default V replace(K key, V value) {
    V curValue;
    if (((curValue = get(key)) != null) || containsKey(key)) {
        curValue = put(key, value);
    }
    return curValue;
}

这样嵌套作业有什么好处吗?这纯粹是一种风格选择吗?为什么不直接在第一次声明curValue时执行任务呢?

代码语言:javascript
复制
// 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接口中的另一个示例:

代码语言:javascript
复制
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;
}
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-02-07 07:57:50

这实际上是复制到一个局部变量,这会产生更小的字节代码,它被看作是一种绝对极端的优化方式,您将在jdk代码中的许多其他地方看到这一点。

另一件事是多次读取局部变量,意味着只读取一次共享变量,例如,如果这是一个volatile,而您只读取它一次并在方法中使用它。

编辑

据我所知,这两种方法的区别是一次读取

假设我们有以下两种方法:

代码语言:javascript
复制
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将具有:

代码语言:javascript
复制
 astore_3 // V curValue = map.get(key); store to curValue
 aload_3  // curValue != null; read the value from curValue

replace方法将是:

代码语言:javascript
复制
 dup      // duplicate whatever value came from map.get(key)
 astore_3 // store the value, thus "consuming" it form the stack

据我理解,dup不算另一次阅读,所以我想这就是所谓的极限优化。

票数 3
EN

Stack Overflow用户

发布于 2018-02-07 01:56:15

生成的字节码几乎没有区别(一个指令差异):https://www.diffchecker.com/okjPcBIb

我写这篇文章是为了生成这些指令,并将它们打印出来:

代码语言:javascript
复制
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的接口。我还做了一个区别:https://www.diffchecker.com/zBVTu7jK

我很有信心JIT会将它们看作完全相同的代码,不管您是在if-语句之外还是在其中初始化变量。

以上所有代码都运行在以下代码上:

代码语言:javascript
复制
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

总之,这是个人喜好。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48654075

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档