Java 8 Map默认实现细节

inc*_*ble 9 java java-8

我经历了新的Java 8 Map方法的默认实现,getOrDefault并注意到一些有点奇怪的东西.考虑例如该getOrDefault方法.它实现如下.

default V getOrDefault(Object key, V defaultValue) {
    V v;
    return ((v = get(key)) != null) || containsKey(key) ? v : defaultValue;
}
Run Code Online (Sandbox Code Playgroud)

现在,这里的"怪异"的东西是"使用的分配结果"模式((v = get(key)) != null.据我所知,这种特殊的模式是不鼓励的,因为它会妨碍可读性.IMO更简洁的版本将是一个类似的东西

default V getOrDefault(Object key, V defaultValue) {
    V v = get(key);
    return v != null || containsKey(key) ? v : defaultValue;
}
Run Code Online (Sandbox Code Playgroud)

我的问题是,除了编码标准/习惯之外,是否有任何特殊理由使用前者而不是后者.特别是,我想知道这两个版本是否跟踪和性能相当?

我唯一可以想象的是编译器可能会确定containsKey通常更快评估并因此首先对其进行评估,但据我所知,短路必须保持执行顺序(至少C的情况) .

编辑:在@ruakh建议之后,这里是两个字节码(由生成javap -c)

  public V getOrDefault(java.lang.Object, V);
    Code:
       0: aload_0
       1: aload_1
       2: invokeinterface #1,  2            // InterfaceMethod get:(Ljava/lang/Object;)Ljava/lang/Object;
       7: dup                               // <-- difference here
       8: astore_3
       9: ifnonnull     22
      12: aload_0
      13: aload_1
      14: invokeinterface #2,  2            // InterfaceMethod containsKey:(Ljava/lang/Object;)Z
      19: ifeq          26
      22: aload_3
      23: goto          27
      26: aload_2
      27: areturn
Run Code Online (Sandbox Code Playgroud)

  public V getOrDefault(java.lang.Object, V);
    Code:
       0: aload_0
       1: aload_1
       2: invokeinterface #1,  2            // InterfaceMethod get:(Ljava/lang/Object;)Ljava/lang/Object;
       7: astore_3
       8: aload_3                           // <-- difference here
       9: ifnonnull     22
      12: aload_0
      13: aload_1
      14: invokeinterface #2,  2            // InterfaceMethod containsKey:(Ljava/lang/Object;)Z
      19: ifeq          26
      22: aload_3
      23: goto          27
      26: aload_2
      27: areturn
Run Code Online (Sandbox Code Playgroud)

我不得不承认,即使经过多年的Java编码,我也不知道如何解释Java字节码.有人可以对这里的差异有所了解吗?

Rol*_*ble 10

这只是一个风格问题.有些人更喜欢最紧凑的代码,而有些人则喜欢更长但更简单的代码.似乎一些在Java核心库上工作的开发人员属于前一组.

就效率而言,两种变体都是相同的.


让我们看看编译器实际上对这两个变体做了什么:

public class ExampleMap<K, V> extends HashMap<K, V> {

    V getOrDefault1(Object key, V defaultValue) {
        V v;
        return ((v = get(key)) != null) || containsKey(key) ? v : defaultValue;
    }

    V getOrDefault2(Object key, V defaultValue) {
        V v = get(key);
        return v != null || containsKey(key) ? v : defaultValue;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在让我们转储生成的字节码,使用javap -c ExampleMap:

Compiled from "ExampleMap.java"
public class ExampleMap<K, V> extends java.util.HashMap<K, V> {
  public ExampleMap();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/util/HashMap."<init>":()V
       4: return

  V getOrDefault1(java.lang.Object, V);
    Code:
       0: aload_0
       1: aload_1
       2: invokevirtual #2                  // Method get:(Ljava/lang/Object;)Ljava/lang/Object;
       5: dup
       6: astore_3
       7: ifnonnull     18
      10: aload_0
      11: aload_1
      12: invokevirtual #3                  // Method containsKey:(Ljava/lang/Object;)Z
      15: ifeq          22
      18: aload_3
      19: goto          23
      22: aload_2
      23: areturn

  V getOrDefault2(java.lang.Object, V);
    Code:
       0: aload_0
       1: aload_1
       2: invokevirtual #2                  // Method get:(Ljava/lang/Object;)Ljava/lang/Object;
       5: astore_3
       6: aload_3
       7: ifnonnull     18
      10: aload_0
      11: aload_1
      12: invokevirtual #3                  // Method containsKey:(Ljava/lang/Object;)Z
      15: ifeq          22
      18: aload_3
      19: goto          23
      22: aload_2
      23: areturn
}
Run Code Online (Sandbox Code Playgroud)

如您所见,代码大多相同.唯一的小差异在于两种方法的第5和第6行.一个只是复制堆栈的顶部值(记住,Java字节码假定基于堆栈的机器模型),而另一个加载来自实例变量的(相同)值.

当Just-in-Time编译器从该字节代码生成真实的机器代码时,它将执行各种优化,例如决定将哪些值写回RAM以及将哪些值保存在CPU寄存器中.我认为可以安全地假设在这些优化发生之后,没有任何差别.