java的 condition ? statement1 : statement2 ,相信大家在日常写代码的时候或多或少都会使用,这个操作可以在一定程度上减少代码量。但是你知道如果使用不当,这个操作会得到意想不到的的结果。我们来看一下下面的这个例子,

public class Test2 {
    public static void main(String[] args) {
        Long number1 = null;
        long number2 = -100;
        int condition = 1;

        Long number  = condition == 1? number1  : number2;
        System.out.println(number);
    }
}

多数人看到上面的代码第一反应应该是输出null,但是真的是这样吗?运行上面的代码后,结果令人大跌眼镜。。

Exception in thread "main" java.lang.NullPointerException
	at Test2.main(Test2.java:7)

上面这么简单的代码竟然抛出了空指针异常,根据异常栈可以大概猜出空指针的原因是number1为null。按照我们的常识上面的代码应该是按照下面的方式来运行

  1. condition == 1 为true
  2. number = number1
  3. 打印出null

但是很显然并不是如此。为了一探究竟,我们来看一下编译好的字节码究竟是怎样的,使用

javap -verbose Test2.class

将上面的代码反编译成字节码形式,

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=6, args_size=1
         0: aconst_null
         1: astore_1
         2: ldc2_w        #2                  // long -100l
         5: lstore_2
         6: iconst_1
         7: istore        4
         9: iload         4
        11: iconst_1
        12: if_icmpne     22
        15: aload_1
        16: invokevirtual #4                  // Method java/lang/Long.longValue:()J
        19: goto          23
        22: lload_2
        23: invokestatic  #5                  // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
        26: astore        5
        28: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
        31: aload         5
        33: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        36: return
      LineNumberTable:
        line 3: 0
        line 4: 2
        line 5: 6
        line 7: 9
        line 8: 28
        line 9: 36
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      37     0  args   [Ljava/lang/String;
            2      35     1 number1   Ljava/lang/Long;
            6      31     2 number2   J
            9      28     4 condition   I
           28       9     5 number   Ljava/lang/Long;
      StackMapTable: number_of_entries = 2
        frame_type = 254 /* append */
          offset_delta = 22
          locals = [ class java/lang/Long, long, int ]
        frame_type = 64 /* same_locals_1_stack_item */
          stack = [ long ]
}
SourceFile: "Test2.java"

我们来看看上面的字节码干了什么,

  1. 0-7行分别是给number1, number2,condition赋值
  2. 9-12将condition和1进行对比,如果不等则跳转到22,否则继续执行
  3. 15-16调用number1的longValue()方法
  4. 其他操作

在第三步的时候因为number1是null,所以抛出了空指针。到这,空指针的原因找到了,那么为什么要调用number1的longValue()方法呢?猜测是因为number1的类型是Long, 而number2的类型是long, java在处理这种情况的时候先将包装类型Long进行拆装,然后再调用Long.valueOf()重新获取包装类型。

为了验证上面的猜想,稍微修改一下代码,将number2的类型修改为Long,

public class Test2 {
    public static void main(String[] args) {
        Long number1 = null;
        Long number2 = -100L;
        int condition = 1;

        Long number  = condition == 1? number1  : number2;
        System.out.println(number);
    }
}

运行代码后可以看到打印null,使用javap查看字节码可以看到没有调用longValue()方法。

基本上印证了我们的猜想。。

总结

在使用condition ? statement1 : statement2的时候,你需要仔细检查是否会出现condition=true,statement1为封装类型,statement2为原始类型的情况,如果出现了这种情况,statement1必须不能为空,否则相当于埋下了一个定时炸弹。