歡迎大家來到Java學習基地~
作者:樂字節
我們已經知道,Java的數據類型分兩種:
- 基本類型:byte,short,int,long,boolean,float,double,char
- 引用類型:所有class和interface類型
引用類型可以賦值為null,表示空,但基本類型不能賦值為null:
<code>String s = null; int n = null; // compile error! /<code>
那麼,如何把一個基本類型視為對象(引用類型)?
比如,想要把int基本類型變成一個引用類型,我們可以定義一個Integer類,它只包含一個實例字段int,這樣,Integer類就可以視為int的包裝類(Wrapper Class):
<code>public class Integer { private int value; public Integer(int value) { this.value = value; } public int intValue() { return this.value; } } /<code>
定義好了Integer類,我們就可以把int和Integer互相轉換:
<code>Integer n = null; Integer n2 = new Integer(99); int n3 = n2.intValue(); /<code>
實際上,因為包裝類型非常有用,Java核心庫為每種基本類型都提供了對應的包裝類型:
基本類型對應的引用類型booleanjava.lang.Booleanbytejava.lang.Byteshortjava.lang.Shortintjava.lang.Integerlongjava.lang.Longfloatjava.lang.Floatdoublejava.lang.Doublecharjava.lang.Character
我們可以直接使用,並不需要自己去定義:
<code>// Integer: /<code>
Run
Auto Boxing
因為int和Integer可以互相轉換:
<code>int i = 100; Integer n = Integer.valueOf(i); int x = n.intValue(); /<code>
所以,Java編譯器可以幫助我們自動在int和Integer之間轉型:
<code>Integer n = 100; // 編譯器自動使用Integer.valueOf(int) int x = n; // 編譯器自動使用Integer.intValue() /<code>
這種直接把int變為Integer的賦值寫法,稱為自動裝箱(Auto Boxing),反過來,把Integer變為int的賦值寫法,稱為自動拆箱(Auto Unboxing)。
注意:自動裝箱和自動拆箱只發生在編譯階段,目的是為了少寫代碼。
裝箱和拆箱會影響代碼的執行效率,因為編譯後的class代碼是嚴格區分基本類型和引用類型的。並且,自動拆箱執行時可能會報NullPointerException:
<code>// NullPointerException /<code>
Run
不變類
所有的包裝類型都是不變類。我們查看Integer的源碼可知,它的核心代碼如下:
<code>public final class Integer { private final int value; } /<code>
因此,一旦創建了Integer對象,該對象就是不變的。
對兩個Integer實例進行比較要特別注意:絕對不能用==比較,因為Integer是引用類型,必須使用equals()比較:
<code>// == or equals? /<code>
Run
仔細觀察結果的童鞋可以發現,==比較,較小的兩個相同的Integer返回true,較大的兩個相同的Integer返回false,這是因為Integer是不變類,編譯器把Integer x = 127;自動變為Integer x = Integer.valueOf(127);,為了節省內存,Integer.valueOf()對於較小的數,始終返回相同的實例,因此,==比較“恰好”為true,但我們絕不能因為Java標準庫的Integer內部有緩存優化就用==比較,必須用equals()方法比較兩個Integer。
按照語義編程,而不是針對特定的底層實現去“優化”。
因為Integer.valueOf()可能始終返回同一個Integer實例,因此,在我們自己創建Integer的時候,以下兩種方法:
- 方法1:Integer n = new Integer(100);
- 方法2:Integer n = Integer.valueOf(100);
方法2更好,因為方法1總是創建新的Integer實例,方法2把內部優化留給Integer的實現者去做,即使在當前版本沒有優化,也有可能在下一個版本進行優化。
我們把能創建“新”對象的靜態方法稱為靜態工廠方法。Integer.valueOf()就是靜態工廠方法,它儘可能地返回緩存的實例以節省內存。
創建新對象時,優先選用靜態工廠方法而不是new操作符。
如果我們考察Byte.valueOf()方法的源碼,可以看到,標準庫返回的Byte實例全部是緩存實例,但調用者並不關心靜態工廠方法以何種方式創建新實例還是直接返回緩存的實例。
進制轉換
Integer類本身還提供了大量方法,例如,最常用的靜態方法parseInt()可以把字符串解析成一個整數:
<code>int x1 = Integer.parseInt("100"); // 100 int x2 = Integer.parseInt("100", 16); // 256,因為按16進制解析 /<code>
Integer還可以把整數格式化為指定進制的字符串:
<code>// Integer: /<code>
Run
注意:上述方法的輸出都是String,在計算機內存中,只用二進制表示,不存在十進制或十六進制的表示方法。int n = 100在內存中總是以4字節的二進制表示:
<code>┌────────┬────────┬────────┬────────┐ │00000000│00000000│00000000│01100100│ └────────┴────────┴────────┴────────┘ /<code>
我們經常使用的System.out.println(n);是依靠核心庫自動把整數格式化為10進制輸出並顯示在屏幕上,使用Integer.toHexString(n)則通過核心庫自動把整數格式化為16進制。
這裡我們注意到程序設計的一個重要原則:數據的存儲和顯示要分離。
Java的包裝類型還定義了一些有用的靜態變量
<code>// boolean只有兩個值true/false,其包裝類型只需要引用Boolean提供的靜態字段: Boolean t = Boolean.TRUE; Boolean f = Boolean.FALSE; // int可表示的最大/最小值: int max = Integer.MAX_VALUE; // 2147483647 int min = Integer.MIN_VALUE; // -2147483648 // long類型佔用的bit和byte數量: int sizeOfLong = Long.SIZE; // 64 (bits) int bytesOfLong = Long.BYTES; // 8 (bytes) /<code>
最後,所有的整數和浮點數的包裝類型都繼承自Number,因此,可以非常方便地直接通過包裝類型獲取各種基本類型:
<code>// 向上轉型為Number: Number num = new Integer(999); // 獲取byte, int, long, float, double: byte b = num.byteValue(); int n = num.intValue(); long ln = num.longValue(); float f = num.floatValue(); double d = num.doubleValue(); /<code>
處理無符號整型
在Java中,並沒有無符號整型(Unsigned)的基本數據類型。byte、short、int和long都是帶符號整型,最高位是符號位。而C語言則提供了CPU支持的全部數據類型,包括無符號整型。無符號整型和有符號整型的轉換在Java中就需要藉助包裝類型的靜態方法完成。
例如,byte是有符號整型,範圍是-128~+127,但如果把byte看作無符號整型,它的範圍就是0~255。我們把一個負的byte按無符號整型轉換為int:
<code>// Byte /<code>
Run
因為byte的-1的二進制表示是11111111,以無符號整型轉換後的int就是255。
類似的,可以把一個short按unsigned轉換為int,把一個int按unsigned轉換為long。
小結
Java核心庫提供的包裝類型可以把基本類型包裝為class;
自動裝箱和自動拆箱都是在編譯期完成的(JDK>=1.5);
裝箱和拆箱會影響執行效率,且拆箱時可能發生NullPointerException;
包裝類型的比較必須使用equals();
整數和浮點數的包裝類型都繼承自Number;
包裝類型提供了大量實用方法。
想了解更多Java乾貨知識,那就關注我吧,每天更新哦~