走進 JDK 之 Boolean

秉心說發表於2019-03-26

往期目錄:

走進 JDK 之 Integer

走進 JDK 之 Long

走進 JDK 之 Float

走進 JDK 之 Byte

今天來說說 BooleanBoolean 類原始碼也很簡單,在閱讀原始碼的過程中思考這麼一個問題,Boolean 型別在記憶體中是如何表示的?或者說,JVM 是如何看待 Boolean 的?

類宣告

public final class Boolean implements java.io.Serializable,Comparable<Boolean>
複製程式碼

Boolean 也是不可變類,事實上所有的基本型別包裝類、StringBigDecimalBigInteger 也都是不可變類。

欄位

private final boolean value; 
public static final Boolean TRUE = new Boolean(true); // true
public static final Boolean FALSE = new Boolean(false); // false
public static final Class<Boolean> TYPE = (Class<Boolean>) Class.getPrimitiveClass("boolean");
private static final long serialVersionUID = -3665804199014368530L;
複製程式碼

Boolean 型別只有兩個值,truefalse

建構函式

public Boolean(boolean value) {
    this.value = value;
}

public Boolean(String s) {
    this(parseBoolean(s));
}
複製程式碼

還是熟悉的味道,第一個建構函式直接傳入 boolean。第二個建構函式呼叫 parseBoolean() 方法,將 String 轉換為布林值。

方法

parseBoolean()

public static boolean parseBoolean(String s) {
    return ((s != null) && s.equalsIgnoreCase("true"));
}
複製程式碼

直接和字串 true 進行比較。

valueOf()

public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

public static Boolean valueOf(String s) {
    return parseBoolean(s) ? TRUE : FALSE;
}
複製程式碼

toString()

public static String toString(boolean b) {
    return b ? "true" : "false";
}
複製程式碼

hashcode()

public static int hashCode(boolean value) {
    return value ? 1231 : 1237;
}
複製程式碼

原始碼都很簡單,沒有什麼好說的。回過頭看看文章開頭的問題:

JVM 是怎麼處理 Boolean 的 ?

原始碼中貌似也看不出什麼端倪,我們得從 Java 虛擬機器的角度出發了。先看下面這個例子:

public class BooleanTest {
    public void test() {
        boolean flag = true;
        if (flag)
            System.out.println("This is true.");
    }
}
複製程式碼

test 方法顯然會列印字串 This is true。站在人腦的思維很容易理解,下面我們站在 JVM 的思維來看一下該如何理解。

首先虛擬機器肯定是不認識這些原始碼的,它認識的只有位元組碼,也就是 class 檔案。關於 Class 檔案的具體格式,可以看看我之前的一篇文章,Class 檔案格式詳解。我在這就直接使用 javap 命令來檢視位元組碼了。

javac BooleanTest.java
javap -v BooleanTest.class
複製程式碼

略去常量池等部分內容,我把 test() 方法的位元組碼內容拿過來:

public void test();
    descriptor: ()V     // 方法描述符
    flags: ACC_PUBLIC   // 訪問標誌
    Code:
      stack=2, locals=2, args_size=1
         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 This is true.
        11: invokevirtual #4    // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        14: return
複製程式碼

下面逐行分析 Code 部分:

stack=2, locals=2, args_size=1
複製程式碼

stack 表示運算元棧深度的最大值,這裡是 2 。locals 表示區域性變數表所需的儲存空間,這裡需要兩個 slot。還記得什麼是 slot 嗎,slot 是虛擬機器為區域性變數分配記憶體所使用的最小單位。args_size 是引數個數,這裡 test() 方法並沒有引數,但是每個方法都有一個引數是指向當前引用自身的。

0: iconst_1     // 將一個 int 常量 1 載入到運算元棧
1: istore_1     // 將數值 1 從運算元棧儲存到區域性變數表
2: iload_1      // 將區域性變數 1 載入到運算元棧
複製程式碼

這三行位元組碼其實就是 boolean flag = true;JVM 並沒有為 boolean 專門做處理,而是直接當做 int 處理。true 就是 1, false 就是 0

3: ifeq          14
...
14: return
複製程式碼

ifeq 是控制轉移指令,這裡的含義是如果運算元棧上的值是 0, 就跳轉到 14 處,14 處指令為 return,則結束方法執行。這裡已經將 1 載入到運算元棧,所以會繼續往下執行,省略號中的位元組碼內容就是列印語言 System.out.println("This is true.");,不作過多分析。

根據 Java 虛擬機器規範,JVM 並沒有任何供 boolean 值專用的位元組碼指令,Java 原始碼中使用到的布林值,在編譯之後都使用 int 值來代替。JVM 也支援 boolean 型別陣列,其一般經過編譯會被當作 byte 陣列進行處理。所以,在位元組碼中,你是看不到 boolean 的。

還記得上篇文章 走進 JDK 之 Byte 中提出的一個問題,作為方法內部區域性變數的 byte 在記憶體中佔幾個位元組 ? 結論是:

基本型別作為方法區域性變數是儲存在棧幀上的,除了 long 和 double 佔兩個 Slot,其他都佔用一個 Slot

JVM 的眼裡,並沒有這麼多的資料型別,對於 booleanbyteshortchar,在編譯期都會變成 int 型別,JVM 也僅僅只對 int 提供了最完整的操作碼,其他型別資料的操作,都是使用相應的 int 型別的操作碼進行操作。那麼 JVM 為什麼沒有給每種資料型別都配置完整的操作碼呢?這還得從操作碼的長度說起。

Java 虛擬機器操作碼的長度為一個位元組,所以位元組碼指令集的操作碼總數不可能超過 256 條。這麼做是為了儘可能獲得短小精幹的位元組碼,位元組碼指令流都是單位元組對齊的,資料量小,傳輸效率高。當然,這麼做的代價就是你不可能設計出一套面向所有資料型別都完整的操作碼。如果每一種資料結構都要得到 Java 虛擬機器的位元組碼指令的支援的話,那麼指令的數量將遠遠超過 256 種。所以,這也給指令集的設計帶來了麻煩。最終權衡的結果就是,只對有限的型別提供完整的指令。大部分的指令都沒有支援 bytecharshortboolean 則更慘,沒有任何指令支援 boolean 型別。對於這些不支援的指令型別,一律使用 int 的相關指令代替。

總結

Boolean 其實是被當做 int 值處理的,true 表示 1false 表示 0

JVMint 提供了完善的操作碼,booleanbytecharshort 在編譯期或執行期都會被轉換為 int,使用 int 型別的位元組碼指令進行處理

文章同步更新於微信公眾號: 秉心說 , 專注 Java 、 Android 原創知識分享,LeetCode 題解,歡迎關注!

![]

走進 JDK 之 Boolean

相關文章