在 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()的主要内容,如果未能解决你的问题,请参考以下文章