導言
在開始學習 JVM 位元組碼之後,遇到了一個有意思的問題,下面這段程式碼,會輸出什麼:
public class Foo {
public static void main(String[] args) {
boolean flag = true;
if (flag) {
System.out.print("A");
}
if (flag == true) {
System.out.print("B");
}
}
}
複製程式碼
這個問題的答案很顯然——會輸出AB
。
接下來重點來了,如果將 2 賦值給 flag 變數,會輸出什麼呢?如果將 flag 賦值為 3 呢?要知道這兩個問題的答案,我們得知道在 JVM 中 boolean 型別的變數是如何表示的,以及在這兩個 if 語句中到底進行了怎樣的判斷。
boolean 型別在 JVM 中是如何表示的
關上上述的示例程式碼,你的編譯器或許會在第二個 if 語句提醒你可以Simplify
,當你執行以後你會發現這個 if 語句的判斷條件與第一個 if 語句是一致的,這就牽扯到 boolean 型別的變數在 JVM 中的表現形式,我們可以通過反編譯得到位元組碼檔案來觀察。
在命令列中輸入javap -c Foo
,得到反編譯的位元組碼如下:
Compiled from "Foo.java"
public class geektime.part1.Foo {
public geektime.part1.Foo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: istore_1
2: iload_1
3: ifeq 14
6: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
9: ldc #3 // String Hello, Java!
11: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
14: iload_1
15: iconst_1
16: if_icmpne 27
19: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
22: ldc #5 // String Hello, JVM!
24: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
27: return
}
複製程式碼
在位元組碼檔案的 main 方法中,第0~2行完成了將一個 int 型別的常量賦值給了第一個變數 flag 的操作。於是我們得到了結論:
- 在 JVM 中 boolean 型別的變數是用數字 0 和 1 來表示的。false 用 0 表示,true 用 1 表示。
if (flag)
然後繼續看位元組碼檔案,接下來的位元組碼就是代表第一個判斷語句if (flag)
語句,從3~11行都屬於第一個 if 程式碼塊,首先是ifeq
。
ifeq
助記符的作用是:當棧頂 int 型數值等於0時跳轉。
此時的棧頂 int 型數值就是剛剛被賦值給 flag 的1,所以在這裡ifeq 14
的意思就是當 flag 等於 0 的時候跳轉到14行,由於第14已經不屬於 if 語句的範圍了,所以這裡的跳轉是不執行 if 語句的意思。也就是說,if(flag)
中是判斷 flag 的值,當 flag 值不等於0的時候才執行 if 中的語句。
if (flag == true)
接下來繼續看第二個判斷語句if (flag == true)
語句,是在位元組碼的14~24行。前面兩行是將一個 int 型別的數值1和 flag 變數推送到棧頂,可以理解為把接下來將要進行比較的 true 放入將要進行比較的一個“容器”,然後是if_icmpne
助記符。
if_icmpne
助記符的作用是:比較棧頂兩int型數值大小,當結果不等於0時跳轉。
現在棧頂兩 int 型的數值是剛剛推送的 true 也就是1,以及 flag 變數,所以if_icmpne
助記符是比較這兩個數值,如果他們相等,執行 if 語句的內容。也就是說,if(flag == true)
中是進行 flag 和 true 的判斷,當它們相等時執行 if 中的語句。
為什麼編譯器提示可以簡化
最後再來討論一下之前說編譯器提示的Simplify
,它會將if (flag == true)
變成if (flag)
,這是為什麼呢?
在沒有Simplify
之前,這兩個 if 語句如果要執行,第一個的條件是 flag 不等0,第二個的條件是 flag 等於1。這是兩個完全不同的比較嘛!但是為什麼在編譯器中可以畫上等號呢?
答案就是,這裡的 flag 是一個 boolean 型別的變數,它只有 true 和 false 兩種值,即在 JVM 中只有0和1兩種值。所以對於一個 boolean 型別的變數,不等0就代表了它一定等1,所以編譯器才會發出可以簡化的提示。
如果將 flag 的值賦為其他整數型值
我們知道在正常情況下編譯器不會接受將2這一個數字賦值給boolean型別變數的這麼一個操作,但是我們可以通過一些其他的工具(如asmtools)來實現這個操作。
-
將flag 的值賦為2。在位元組碼中就是將 iconst_2 賦值給 flag 時,輸出為空。
-
將 flag 的值賦為3。在位元組碼中就是將 iconst_3 賦值給 flag 時,輸出為
AB
。 -
如果再多做幾個實驗,將 flag 的值賦為4,輸出為空;將 flag 的值賦為5,輸出為
AB
......
如果我們將這些整數都轉化為二進位制,即2=0010,3=0011,4=0100,5=0101。
當二進位制末尾為0時,無輸出,當二進位制末尾為1時,輸出AB
。
由此我們可以得出結論:如果將其他整數型別的值賦值給一個 boolean 型別的變數,虛擬機器會取此整數值二進位制的最後一位。