關於 JVM 中原始型別 boolean 的討論

語言: CN / TW / HK

導言

在開始學習 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)來實現這個操作。

  1. 將flag 的值賦為2。在位元組碼中就是將 iconst_2 賦值給 flag 時,輸出為空。

  2. 將 flag 的值賦為3。在位元組碼中就是將 iconst_3 賦值給 flag 時,輸出為AB

  3. 如果再多做幾個實驗,將 flag 的值賦為4,輸出為空;將 flag 的值賦為5,輸出為AB......

如果我們將這些整數都轉化為二進位制,即2=0010,3=0011,4=0100,5=0101。

當二進位制末尾為0時,無輸出,當二進位制末尾為1時,輸出AB

由此我們可以得出結論:如果將其他整數型別的值賦值給一個 boolean 型別的變數,虛擬機器會取此整數值二進位制的最後一位。