Java 为什么在比较中使用赋值?

Java 为什么在比较中使用赋值?,java,Java,在阅读源代码时,我在JDK源代码中偶然发现了这种方法。请注意v和newValue的声明和初始化。我们这里有“很好”的未定义值,比较中的赋值,这是“很好”的,还有额外的括号以降低可读性。和其他代码的气味 default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) { Objects.requireNonNull(mappingFunction); V v;

在阅读源代码时,我在JDK源代码中偶然发现了这种方法。请注意
v
newValue
的声明和初始化。我们这里有“很好”的未定义值,比较中的赋值,这是“很好”的,还有额外的括号以降低可读性。和其他代码的气味

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;
}

除了炫耀Java构造之外,还有什么实际好处我不知道,而不是采用“简单”的方式吗?

我同意:这是一种代码味道,我肯定更喜欢第二个版本

在单个表达式中神秘地使用赋值和比较可能会导致代码缩短,以防出现循环,例如:

V;
而((v=getNext())!=null){
//…用v做点什么。。。
}
要消除代码气味,需要更多代码,并且需要在两个位置分配变量:

V=getNext();
while(v!=null){
// ...
v=getNext();
}
或者您需要在分配后移动循环退出条件:

default V computeIfAbsent(K key,
        Function<? super K, ? extends V> mappingFunction) {
    V v, newValue;
    return ((v = get(key)) == null &&
            (newValue = mappingFunction.apply(key)) != null &&
            (v = putIfAbsent(key, newValue)) == null) ? newValue : v;
}
while(true){
V=getNext();
如果(v==null)
打破
// ...
}

对于if语句来说,这显然毫无意义。即使是循环,我也会避免它。

我认为这主要是偏好的问题;我觉得Doug Lea更喜欢这种简洁的风格。但是如果你看一下这个方法的用法,它会更有意义,因为它与需要内联赋值的
newValue
成对出现:

default V computeIfAbsent(K key,
        Function<? super K, ? extends V> mappingFunction) {
    V v, newValue;
    return ((v = get(key)) == null &&
            (newValue = mappingFunction.apply(key)) != null &&
            (v = putIfAbsent(key, newValue)) == null) ? newValue : v;
}
default V computeIfAbsent(K键,
函数(但对于标准库,它可能很重要),并且:

#惯性:这种模式在90年代的C程序员中很常见,因此计算机科学的巨人们可能仍然使用这种风格

除非性能非常关键,否则为新的业务逻辑编写这样的代码毫无意义


(微观)优化:
javac
(JDK 11)为原始(“坏”)版本生成的字节码比(更好)的代码少一个JVM操作。为什么?JDK的版本“使用”赋值运算符的返回值(而不是从变量加载值)进行
if
条件评估。

然而,这更多的是
javac
优化可能性的限制,而不是编写可读性较差的代码的原因

以下是问题中引用的JDK版本的字节码:

   0: aload_2
   1: invokestatic  #2                  // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
   4: pop
   5: aload_0
   6: aload_1
   7: invokevirtual #3                  // Method get:(Ljava/lang/Object;)Ljava/lang/Object;
  10: dup
  11: astore_3
  12: ifnonnull     39
  15: aload_2
  16: aload_1
  17: invokeinterface #4,  2            // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
  22: dup
  23: astore        4
  25: ifnull        39
  28: aload_0
  29: aload_1
  30: aload         4
  32: invokevirtual #5                  // Method put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
  35: pop
  36: aload         4
  38: areturn
  39: aload_3
  40: areturn
以下是更可读版本的字节码:

public V computeIfAbsent(K key,
                         Function<? super K, ? extends V> mappingFunction) {
    Objects.requireNonNull(mappingFunction);
    final V v = get(key);
    if (v == null) {
        final V newValue = mappingFunction.apply(key);
        if (newValue != null) {
            put(key, newValue);
            return newValue;
        }
    }

    return v;
}

如果
v
不是本地的,它可以防止在设置和读取之间更改
v
的比赛条件。但这里唯一的优势似乎是吓跑了人们不要触摸代码:-)这是来自
ConcurrentMap
,对吗?您可能希望链接到确切的源代码和版本以明确这一点。我在许多JDK类中都见过这种情况;对于作者来说,它可能是惯用的,但并不真正遵守严格的风格指南。无论如何,这是一个相关的问题。在风格上完全是意见分歧。这只是一种特殊的代码风格(可能是Doug Lea的?它会打破:“在if和while条件中避免赋值(``='')”。不会在您自己的代码中推荐此选项,原因与您概述的完全相同。此问题不应以基于意见的方式结束。它不是询问意见,而是客观地询问是否有好处,或者这是否是一个意见问题。和往常一样,亲密的选民在无休止地点击问题并试图感觉自己很重要的时候,尽了最小的努力。我不会说这是神秘的。无处不在,但可能不推荐使用?它让我想起了旧的C代码,比如
if(t=p->next).
,它还依赖于非空指针到true的隐式转换,而空指针到false的隐式转换。但你是对的:奥术不是正确的词。+1是一个很好的例子,说明了这种构造确实可以使代码更可读/更少干涸。我相信这是正确的答案。我认为,JDK-devels不能驱动,至少不应该基于自己的偏好驱动他们的工作,而是基于测量的性能。核心库,如集合,必须快速。因此,微观优化在这里是有意义的。我的眼睛还是有点痛,但这只是我习惯于看的习惯。因此,我再次相信这是正确的答案。谢谢你帮我理解它!虽然这可能是一个优化,但我认为答案更有意义。只有作者知道真正的原因。“这与其说是编写可读性较差的代码的原因,不如说是对javac优化可能性的限制”-Optimizer的限制难道不是编写可读性较差的代码的可能原因吗?@Dukeling:当然,这就是优秀的优化器至关重要的全部原因:这不是让程序员的代码运行得更快的问题,而是阻止程序员为了性能而编写反惯用、不可读的代码。说真的,你们不能通过查看字节码来对代码的“最佳性”做出有意义的陈述。实际执行的是本机代码。JIT获取这些字节码,收集它们的解释器统计数据,然后生成优化的本机代码指令。如果您想尝试根据编译器输出分析代码性能,请查看本机代码。
   0: aload_2
   1: invokestatic  #2                  // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
   4: pop
   5: aload_0
   6: aload_1
   7: invokevirtual #3                  // Method get:(Ljava/lang/Object;)Ljava/lang/Object;
  10: astore_3
  11: aload_3
  12: ifnonnull     40
  15: aload_2
  16: aload_1
  17: invokeinterface #4,  2            // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
  22: astore        4
  24: aload         4
  26: ifnull        40
  29: aload_0
  30: aload_1
  31: aload         4
  33: invokevirtual #5                  // Method put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
  36: pop
  37: aload         4
  39: areturn
  40: aload_3
  41: areturn