Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/364.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
为什么JavaAPI在if语句中有看似奇怪的赋值?_Java_Java 8 - Fatal编程技术网

为什么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

我不熟悉编程和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 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
variable
map.get(..)
;这是多线程代码btw中的常见模式,在此模式下,您将读取一次并对已读取的值进行操作,然后在stack@Ivan这里可能有更好的方式来表达同样的话(我今天早上可能会很慢)…性能并不是这里唯一的因素。为什么有人会喜欢这种只出现在某些区域的嵌套分配方式?有些人可能会认为这是为了性能,你说的是性能似乎没有明显的提高。这些方法是由同一个开发人员提出的,所以问题仍然存在:这是怎么回事e我们还没有注意到设计/可读性问题,或者可能是一些您尚未发现测试的深层次优化。可能需要分析JITed输出,以真正确认您的“个人偏好”要求。(续)更不用说,一些开发人员知道未来的更新,他们编写代码为下一次更新做准备。一些Java 8功能被推迟,因此这可能是Java 9中的优势。如果您想完全揭穿与性能的关系,请尝试Java 9并上传JIT日志(甚至只使用JITWatch)。如果你不想,我会理解,对于可能相同的答案,这将是多余的工作。我只是觉得,只分析一个版本的字节码并不能真正解释这个谜团。@VinceEmigh;你认为在外部和内部分配一个变量会导致深层次的优化,而这不是在说明中发现的,而是在JIT中,它是一个赋值。在函数的末尾,它返回结果。这就是它。没有别的东西了。我们甚至有其他语言,它是一种C和C++固有的风格。有一种情况会被优化出来,那就是短手评价。例如:<代码>(条件)(err=someFunc())==null))
。如果
条件为真,
someVar
将被取消