在 Java Lambda 中,为啥在捕获的变量上调用 getClass()

Posted

技术标签:

【中文标题】在 Java Lambda 中,为啥在捕获的变量上调用 getClass()【英文标题】:In Java Lambda's why is getClass() called on a captured variable在 Java Lambda 中,为什么在捕获的变量上调用 getClass() 【发布时间】:2017-08-24 05:56:37 【问题描述】:

如果你查看字节码

Consumer<String> println = System.out::println;

Java 8 update 121 生成的字节码是

GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
DUP
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
POP
INVOKEDYNAMIC accept(Ljava/io/PrintStream;)Ljava/util/function/Consumer; [
  // handle kind 0x6 : INVOKESTATIC
  java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  // arguments:
  (Ljava/lang/Object;)V, 
  // handle kind 0x5 : INVOKEVIRTUAL
  java/io/PrintStream.println(Ljava/lang/String;)V, 
  (Ljava/lang/String;)V
]
ASTORE 1

正在对System.out 调用getClass() 方法,结果被忽略。

这是间接空引用检查吗?

如果你跑的话当然可以

PrintStream out = null;
Consumer<String> println = out::println;

这会触发 NullPointerException。

【问题讨论】:

Peter Lawrey 的问题,Holger 的回答,Shipilev 打开的错误;这个问题让我很开心。 【参考方案1】:

是的,调用getClass() 已成为规范的“测试null”习语,因为getClass() 预计将是一种廉价的内在操作,我想,HotSpot 可能能够检测到这种模式并减少如果不使用 getClass() 的结果,则对内部 null-check 操作进行操作。

另一个例子是用一个不是this的外部实例创建一个内部类实例:

public class ImplicitNullChecks 
    class Inner 
    void createInner(ImplicitNullChecks obj) 
        obj.new Inner();
    

    void lambda(Object o) 
        Supplier<String> s=o::toString;
    

编译成

Compiled from "ImplicitNullChecks.java"
public class bytecodetests.ImplicitNullChecks 
  public bytecodetests.ImplicitNullChecks();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  void createInner(bytecodetests.ImplicitNullChecks);
    Code:
       0: new           #23                 // class bytecodetests/ImplicitNullChecks$Inner
       3: dup
       4: aload_1
       5: dup
       6: invokevirtual #24                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
       9: pop
      10: invokespecial #25                 // Method bytecodetests/ImplicitNullChecks$Inner."<init>":(Lbytecodetests/ImplicitNullChecks;)V
      13: pop
      14: return

  void lambda(java.lang.Object);
    Code:
       0: aload_1
       1: dup
       2: invokevirtual #24                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
       5: pop
       6: invokedynamic #26,  0             // InvokeDynamic #0:get:(Ljava/lang/Object;)Ljava/util/function/Supplier;
      11: astore_2
      12: return

另见JDK-8073550:

我们的类库中的一些地方使用了使用 object.getClass() 来检查 null 的奇怪技巧。 虽然这看起来是一个聪明的举动,但它实际上让人们误以为这是一个被批准的 空值检查的实践。

在 JDK 7 中,我们有 Objects.requireNonNull 提供正确的 null 检查,并声明 意图正确。

这是否也适用于编程语言内在检查可能存在争议,因为为此目的使用Objects.requireNonNull 会创建对源代码中不可见的java.lang 包之外的类的依赖。在这种特殊情况下,只有查看字节码的人才能看到这个技巧。但已决定使用 Java 9 来改变这种行为。

这就是jdk1.9.0b160 编译同一个测试类的方式:

Compiled from "ImplicitNullChecks.java"
public class bytecodetests.ImplicitNullChecks 
  public bytecodetests.ImplicitNullChecks();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  void createInner(bytecodetests.ImplicitNullChecks);
    Code:
       0: new           #26                 // class bytecodetests/ImplicitNullChecks$Inner
       3: dup
       4: aload_1
       5: dup
       6: invokestatic  #27                 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
       9: pop
      10: invokespecial #28                 // Method bytecodetests/ImplicitNullChecks$Inner."<init>":(Lbytecodetests/ImplicitNullChecks;)V
      13: pop
      14: return

  void lambda(java.lang.Object);
    Code:
       0: aload_1
       1: dup
       2: invokestatic  #27                 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
       5: pop
       6: invokedynamic #29,  0             // InvokeDynamic #0:get:(Ljava/lang/Object;)Ljava/util/function/Supplier;
      11: astore_2
      12: return

【讨论】:

谢谢您,您的见解一如既往的宝贵。 如果有一个简单的nullcheck字节码指令可能会很好。 @David Conrad:不需要那么频繁,专用检查指令相对于单个调用指令(对于众所周知的方法)的优势并不大。作为一个净收益,如果 JVM 识别 Objects.requireNonNull 并在本质上处理这些调用,那么显式调用它的 Java 代码也会从中受益。 @holi-java:我不确定,你期望什么样的外部参考。讨论的编译形式可以被每个人验证,例如使用this class on Ideone,引用的JDK-8073550已经链接,如果你想更深入,你可以从this mail开始并关注讨论。 @Lii 我注意到NullPointerException.getMessage() 已被覆盖。不幸的是,它在这里没有用,除非此功能特别对待Objects.requireNonNull 并分析调用者。 outer.new Inner() 的例子就在这里,在我的回答中。

以上是关于在 Java Lambda 中,为啥在捕获的变量上调用 getClass()的主要内容,如果未能解决你的问题,请参考以下文章

如何使用lambda表达式捕获局部变量?

20145207《Java程序设计》第7周学习总结

Java 基础语法详解 Java 的 Lambda 表达式

Java 基础语法详解 Java 的 Lambda 表达式

Java 基础语法详解 Java 的 Lambda 表达式

更方便的函数回调——Lambda