在使用Optional时发现一个场景如下代码所示:(简化后的代码)
public static class Content {
private String a;
public void setA(String a) {
this.a = a;
}
}
public static void main(String[] args) {
Content c = null;
String str = null;
Optional.ofNullable(str).ifPresent(c::setA);
}
这段代码本意是要在str为空的时候不要调用c.setA()方法,那么此时即使c为null也不会产生异常。但是在实际运行过程中却发现会抛出NullPointerException异常。
Exception in thread "main" java.lang.NullPointerException
at com.test.Test.main(Test.java:18)
代码已经简化的很简单了基本找不到什么问题,初步猜测应该是Java语法糖在编译成字节码的过程中加入了一些其他指令。接下来使用javap命令反解析class文件得到最终生成的指令内容,内容如下:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: aconst_null
1: astore_1
2: aconst_null
3: astore_2
4: aload_2
5: invokestatic #21 // Method java/util/Optional.ofNullable:(Ljava/lang/Object;)Ljava/util/Optional;
8: aload_1
9: dup
10: invokevirtual #22 // Method java/lang/Object.getClass:()Ljava/lang/Class;
13: pop
14: invokedynamic #23, 0 // InvokeDynamic #0:accept:(Lcom/test/Test$Content;)Ljava/util/function/Consumer;
19: invokevirtual #24 // Method java/util/Optional.ifPresent:(Ljava/util/function/Consumer;)V
22: return
LineNumberTable:
line 71: 0
line 72: 2
line 73: 4
line 74: 22
LocalVariableTable:
Start Length Slot Name Signature
0 23 0 args [Ljava/lang/String;
2 21 1 c Lcom/test/Test$Content;
4 19 2 str Ljava/lang/String;
果不其然在10:行执行Optional.ofNullable()方法后,ifPresent之前,对变量c执行了一个c.getClass()的方法调用。由于在此处c为null,所以抛出了空指针异常。程序报错的原因就在这里,那么这里为什么要执行getClass操作呢?
有意思的是小伙伴在Java11执行的结果是:
Exception in thread "main" java.lang.NullPointerException
at java.util.Objects.requireNonNull(Objects.java:203)
at com.test.Test.main(Test.java:18)
解析字节码得到的内容发现10:行由
【invokevirtual #22 // Method java/lang/Object.getClass:()Ljava/lang/Class;】
变成了
【 invokestatic #27 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;】,
那么也就是说这里可能是要进行一个非空校验。
在openjdk的记录里变更原因也证实了这个猜想
【https://bugs.openjdk.java.net/browse/JDK-8073479#】
所以在使用方法引用的时候一定要注意这个问题,在代码没有执行到的情况下也会抛出空指针异常呦
网友评论