三目运算是我们日常开发中经常用到的一个运算符号.它可以让我们很优雅地根据条件对一个变量进行赋值,灵活又优雅,但稍微不慎就会导致空指针异常.
首先我们来看一下阿里编程规范中的例子:
@GetMapping("/v1")
public String v1() {
Integer a = 1;
Integer b = 2;
Integer c = null;
Boolean flag = false;
// a * b 的结果是 int 类型,那么 c 会强制拆箱成 int 类型,抛出 NPE 异常
Integer result = (flag ? a * b : c);
return "hello world";
}
调用这段代码一不小心就会因为java的自动包装引起的NPE异常,为了保证类型一致,编译器在编译的时候会帮我们隐式调用,这些细节点我们得阅读class字节码才可以知道得
我们使用idea查看v1方法的字节码并借阅JVM虚拟机指令表可以了解到一些细节
public v1()Ljava/lang/String;
@Lorg/springframework/web/bind/annotation/GetMapping;(value={"/v1"})
L0
LINENUMBER 17 L0
ICONST_1
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
ASTORE 1
// 第17行 步骤L0
// 1 将一个int类型值1放入栈顶
// 2 调用Integer.valueOf方法将栈顶int 1转换为Interger 1 并赋值给第1个本地变量
L1
LINENUMBER 18 L1
ICONST_2
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
ASTORE 2
// 第18行 步骤L1
// 1 将一个int类型值2放入栈顶
// 2 调用Integer.valueOf方法将栈顶int 2转换为Interger 2 并赋值给第2个本地变量
L2
LINENUMBER 19 L2
ACONST_NULL
ASTORE 3
// 第19行 步骤L2
// 将null赋值给本地第3个本地变量
L3
LINENUMBER 20 L3
ICONST_0
INVOKESTATIC java/lang/Boolean.valueOf (Z)Ljava/lang/Boolean;
ASTORE 4
// 第20行 步骤L3
// 将一个int类型值0放入栈顶并调用Boolean.valueOf方法转换为Boolean false,并赋值给本地第4个变量
L4
LINENUMBER 22 L4
ALOAD 4
INVOKEVIRTUAL java/lang/Boolean.booleanValue ()Z
IFEQ L5
ALOAD 1
INVOKEVIRTUAL java/lang/Integer.intValue ()I
ALOAD 2
INVOKEVIRTUAL java/lang/Integer.intValue ()I
IMUL
GOTO L6
L5
FRAME FULL [com/wkx/study/controller/TestController java/lang/Integer java/lang/Integer java/lang/Integer java/lang/Boolean] []
ALOAD 3
INVOKEVIRTUAL java/lang/Integer.intValue ()I
L6
FRAME SAME1 I
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
ASTORE 5
// 步骤 L4到L6是三目运算符的字节码类似于if else表达句
// 1 将本地第四个变量转换为boolean类型
// 2 如果上一步转换的boolean类型值为true的话
// 2.1 将本地第1跟第2个变量取出并调用Integer.intValue拆箱变为int
// 2.2 将这两个值进行计算,并将计算结果装箱赋值给第5个本地变量
// 3 如果为false的话
// 3.1 将本地第3个变量拆箱转为int
// 3.2 将上一步拆箱获取的int在装箱转为Integer
L7
LINENUMBER 23 L7
LDC "hello world"
ARETURN
我们从上面的字节码知道
1 两个Integer进行乘法运算的时候会先转为int来进行计算,因为Integer不具备这个能力
2 三目运算符其实在转为字节码中相当于一个if else操作(如果上面例子的flag是true的话就不会NPE异常了)
3 在进行三目运算符的时候,编译器为了保证类型一致会将冒号前后的保证类型转为基本数据类型(将上面的例子改为 Integer k = Boolean ? int : Integer;也会将先拆箱转为int再转装箱转为Integer )