三目運(yùn)算是我們?nèi)粘i_發(fā)中經(jīng)常用到的一個(gè)運(yùn)算符號.它可以讓我們很優(yōu)雅地根據(jù)條件對一個(gè)變量進(jìn)行賦值,靈活又優(yōu)雅,但稍微不慎就會導(dǎo)致空指針異常.
首先我們來看一下阿里編程規(guī)范中的例子:
@GetMapping("/v1")
public String v1() {
Integer a = 1;
Integer b = 2;
Integer c = null;
Boolean flag = false;
// a * b 的結(jié)果是 int 類型,那么 c 會強(qiáng)制拆箱成 int 類型,拋出 NPE 異常
Integer result = (flag ? a * b : c);
return "hello world";
}
調(diào)用這段代碼一不小心就會因?yàn)閖ava的自動包裝引起的NPE異常,為了保證類型一致,編譯器在編譯的時(shí)候會幫我們隱式調(diào)用,這些細(xì)節(jié)點(diǎn)我們得閱讀class字節(jié)碼才可以知道得
我們使用idea查看v1方法的字節(jié)碼并借閱JVM虛擬機(jī)指令表可以了解到一些細(xì)節(jié)
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 將一個(gè)int類型值1放入棧頂
// 2 調(diào)用Integer.valueOf方法將棧頂int 1轉(zhuǎn)換為Interger 1 并賦值給第1個(gè)本地變量
L1
LINENUMBER 18 L1
ICONST_2
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
ASTORE 2
// 第18行 步驟L1
// 1 將一個(gè)int類型值2放入棧頂
// 2 調(diào)用Integer.valueOf方法將棧頂int 2轉(zhuǎn)換為Interger 2 并賦值給第2個(gè)本地變量
L2
LINENUMBER 19 L2
ACONST_NULL
ASTORE 3
// 第19行 步驟L2
// 將null賦值給本地第3個(gè)本地變量
L3
LINENUMBER 20 L3
ICONST_0
INVOKESTATIC java/lang/Boolean.valueOf (Z)Ljava/lang/Boolean;
ASTORE 4
// 第20行 步驟L3
// 將一個(gè)int類型值0放入棧頂并調(diào)用Boolean.valueOf方法轉(zhuǎn)換為Boolean false,并賦值給本地第4個(gè)變量
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是三目運(yùn)算符的字節(jié)碼類似于if else表達(dá)句
// 1 將本地第四個(gè)變量轉(zhuǎn)換為boolean類型
// 2 如果上一步轉(zhuǎn)換的boolean類型值為true的話
// 2.1 將本地第1跟第2個(gè)變量取出并調(diào)用Integer.intValue拆箱變?yōu)閕nt
// 2.2 將這兩個(gè)值進(jìn)行計(jì)算,并將計(jì)算結(jié)果裝箱賦值給第5個(gè)本地變量
// 3 如果為false的話
// 3.1 將本地第3個(gè)變量拆箱轉(zhuǎn)為int
// 3.2 將上一步拆箱獲取的int在裝箱轉(zhuǎn)為Integer
L7
LINENUMBER 23 L7
LDC "hello world"
ARETURN
我們從上面的字節(jié)碼知道
1 兩個(gè)Integer進(jìn)行乘法運(yùn)算的時(shí)候會先轉(zhuǎn)為int來進(jìn)行計(jì)算,因?yàn)镮nteger不具備這個(gè)能力
2 三目運(yùn)算符其實(shí)在轉(zhuǎn)為字節(jié)碼中相當(dāng)于一個(gè)if else操作(如果上面例子的flag是true的話就不會NPE異常了)
3 在進(jìn)行三目運(yùn)算符的時(shí)候,編譯器為了保證類型一致會將冒號前后的保證類型轉(zhuǎn)為基本數(shù)據(jù)類型(將上面的例子改為 Integer k = Boolean ? int : Integer;也會將先拆箱轉(zhuǎn)為int再轉(zhuǎn)裝箱轉(zhuǎn)為Integer )