美文网首页
三目运算符自动拆箱NPE分析

三目运算符自动拆箱NPE分析

作者: 但时间也偷换概念 | 来源:发表于2019-11-22 21:57 被阅读0次

    背景

    在笔者工作中曾经遇到过一次NPE异常,导致此异常的原因查到最后发现是三目运算符自动装箱导致的,我们来分析一下这个case。

    首先我们来还原一下当时大致是怎样一个case。

      public class NpeTest {
    
        private Boolean bool;
    
        public Boolean getBool() {
            return bool;
        }
    
        public void setBool(Boolean bool) {
            this.bool = bool;
        }
    
        public static void main(String[] args) {
            NpeTest npeTest = new NpeTest();
            npeTest.setBool(null);
    
            Boolean test = npeTest != null ? npeTest.getBool() : false;
            System.out.println(test);
        }
    
    
    }
    
    

    如上述代码,当时大概是写了一个类似的三目运算符逻辑,然后在这里发生了NPE空指针异常。

    分析

    jls-15.25标准中有如下定义:

    If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression.
    
      If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T.
    
     If one of the second and third operands is of the null type and the type of the other is a reference type, then the type of the conditional expression is that reference type.
    

    我们来反编译最上面的代码看一下。
    首先在iterm终端进入目标类路径,输入以下命令编译与反编译NpeTest类。

    javac NpeTest.java 
    javap -c NpeTest
    

    最后输出信息如下:

    public class org.springside.modules.utils.NpeTest {
      public org.springside.modules.utils.NpeTest();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public java.lang.Boolean getBool();
        Code:
           0: aload_0
           1: getfield      #2                  // Field bool:Ljava/lang/Boolean;
           4: areturn
    
      public void setBool(java.lang.Boolean);
        Code:
           0: aload_0
           1: aload_1
           2: putfield      #2                  // Field bool:Ljava/lang/Boolean;
           5: return
    
      public static void main(java.lang.String[]);
        Code:
           0: new           #3                  // class org/springside/modules/utils/NpeTest
           3: dup
           4: invokespecial #4                  // Method "<init>":()V
           7: astore_1
           8: aload_1
           9: aconst_null
          10: invokevirtual #5                  // Method setBool:(Ljava/lang/Boolean;)V
          13: aload_1
          14: ifnull        27
          17: aload_1
          18: invokevirtual #6                  // Method getBool:()Ljava/lang/Boolean;
          21: invokevirtual #7                  // Method java/lang/Boolean.booleanValue:()Z
          24: goto          28
          27: iconst_0
          28: invokestatic  #8                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
          31: astore_2
          32: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
          35: aload_2
          36: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
          39: return
    }
    
    

    可以看到21后面跟的是booleanValue
    所以基本可以确定是一个拆箱操作了
    于是我们可以确定是因为三目运算符中,第二位是Boolean包装类型,第三位是boolean基本数据类型,所以进行了拆箱。
    那么我们改一下再来看一下

    public class NpeTest {
    
        private Boolean bool;
    
        public Boolean getBool() {
            return bool;
        }
    
        public void setBool(Boolean bool) {
            this.bool = bool;
        }
    
        public static void main(String[] args) {
            NpeTest npeTest = new NpeTest();
            npeTest.setBool(null);
    
            Boolean test = npeTest != null ? npeTest.getBool() : Boolean.FALSE;
            System.out.println(test);
        }
    
    
    }
    
    

    重新执行一下javac和javap

    public class org.springside.modules.utils.NpeTest {
      public org.springside.modules.utils.NpeTest();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public java.lang.Boolean getBool();
        Code:
           0: aload_0
           1: getfield      #2                  // Field bool:Ljava/lang/Boolean;
           4: areturn
    
      public void setBool(java.lang.Boolean);
        Code:
           0: aload_0
           1: aload_1
           2: putfield      #2                  // Field bool:Ljava/lang/Boolean;
           5: return
    
      public static void main(java.lang.String[]);
        Code:
           0: new           #3                  // class org/springside/modules/utils/NpeTest
           3: dup
           4: invokespecial #4                  // Method "<init>":()V
           7: astore_1
           8: aload_1
           9: aconst_null
          10: invokevirtual #5                  // Method setBool:(Ljava/lang/Boolean;)V
          13: aload_1
          14: ifnull        24
          17: aload_1
          18: invokevirtual #6                  // Method getBool:()Ljava/lang/Boolean;
          21: goto          27
          24: getstatic     #7                  // Field java/lang/Boolean.FALSE:Ljava/lang/Boolean;
          27: astore_2
          28: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
          31: aload_2
          32: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
          35: return
    }
    
    

    可以看到booleanValue没有了,问题解决。

    相关文章

      网友评论

          本文标题:三目运算符自动拆箱NPE分析

          本文链接:https://www.haomeiwen.com/subject/yqccwctx.html