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