衆所周知,java 是一門面向對象的高級語言。但是 java 中的基本類型不能作爲對象使用,爲了解決對象的調用問題,爲每個基本類型創造了對應的包裝類型。
先來看一道包裝類的題目吧
int a = 10;
Integer b = 10;
System.out.println(a == b); ①
System.out.println(b.equals(a)); ②
Integer c = 100;
Integer d = 100;
System.out.println(c == d); ③
System.out.println(c.equals(d)); ④
Integer e = new Integer(100);
Integer f = new Integer(100);
System.out.println(e == f); ⑤
System.out.println(e.equals(f)); ⑥
Integer g = 1000;
Integer h = 1000;
System.out.println(g == h); ⑦
System.out.println(g.equals(h)); ⑧
在看知識點前先看一下習題,看看是否存在不瞭解的點,以便對症下藥
這些題考查的都是 java 中很基礎的一類概念
包括包裝類與基本類型的轉換,包括自動拆箱裝箱
以及 == 和 equals 的區別
給上答案
①true ②true ③true ④true ⑤false ⑥true ⑦false ⑧true
以下示範都以Integer爲例,其他包裝類型與之無明顯差異,大家看會了 Integer 其他包裝類也就都能理解了
爲什麼需要包裝類
- 基本類的值不屬於對象,因此基本類型只存在一個值,沒有各種屬性,也沒有各種方法可以調用。
- 很多場景需要使用對象,比如 java 中的集合,所以爲了能使基本類型的值也能存入集合中,或者用於其他類中,則需要把它們包裝成對象。
- 每個非基本類都繼承自基類 Object ,所以天生神力,自帶一身方法。但是基本類型沒有方法可以調用,用包裝類可以繼承 Object 方法,也可以添加自身方法。
- 非基本類型變量不賦值默認爲 null,但基本類型沒有 null 值,比如 int 默認爲 0,Boolean 默認爲 false。
- 在某些場合中,我們可能會希望使用到 null 值,表示某些意義,那基本類型則就做不到了。
包裝類與基本類型的轉換 (包括自動裝拆箱)
JDK自從1.5之後就引入了自動裝拆箱的功能,這大大簡化了開發人員的代碼複雜度,使得對基本類型的代碼書寫效率大大提高。
但是這也帶來了一個問題,很多小白搞不明白基本類型和包裝類型,使很多開發人員對此形成誤解。
首先,最基本的,用 new 來構造一個 Integer 對象
再用 Integer 的 intValue() 方法返回它的int值
Integer a = new Integer(1);
int b = a.intValue();
但在 java 中一般不提倡 new 一個 Integer 包裝類對象
用 Integer.valueOf() 靜態方法來獲取一個 Integer 對象
a = Integer.valueOf(5);
Integer a = 5; // 也可以寫成
而這個方法即是 int --> Integer 的自動裝箱方法 (即上面兩行代碼是等同的)
同理自動拆箱
Integer a = 5;
// int b = a.intValue();
int b = a; // 這行代碼與上面一行代碼等同
比如之前題目中的①和② 就有許多裝拆箱的知識點
int a = 10;
Integer b = 10; // 自動裝箱
// 將b自動拆箱與a比較
System.out.println(a == b);
// 將a自動裝箱成爲對象才能作爲equals中的參數
System.out.println(b.equals(a));
但是在自動裝箱需要注意一個細節,也就是之前題目中的③④⑦⑧。
在 -128 ~ 127 的數中,也就是 byte 的取值範圍,自動裝箱時,會從常量池中去取Integer對象,因而包裝出的 Integer 對象是同一個對象,不在這個範圍內的數字,包裝出的 Integer 對象則都是 new 出來的新對象。
Integer c = 100; // 從常量池中取出Integer對象
Integer h = 1000; // 相當於用new創建出一個新對象
因此在之前題目中值爲 100 的 Integer 對象 == 爲 true,而值爲 1000 的Integer對象則爲 false。
基本類型和包裝類型與String的轉換
由於存在自動裝拆箱的功能,所以對於 String 與數字轉換時,可以將其轉換爲基本類型 int,也可以轉換爲包裝類 Integer,通過自動裝拆箱即可以正確賦值給變量
String 和 Integer 之間的轉換
// String --> Integer
Integer a = new Integer("12345");
// Integer --> String
String s = a.toString();
String 和 int 之間的轉換
// String --> int
int a = Integer.parseInt("12345");
// String --> Integer --> int
a = new Integer("12345");
// int --> Integer --> String
String s = a + ""; //自動裝箱調用toString()
s = (Integer.valueOf(a)).toString();
包裝類基礎
基本類 | 包裝類 | 繼承 | 基本類類信息 | 包裝類對應類信息 |
---|---|---|---|---|
byte | Byte | Number | byte.class | Byte.TYPE |
short | Short | Number | short.class | Short.TYPE |
int | Integer | Number | int.class | Integer.TYPE |
long | Long | Number | long.class | Long.TYPE |
float | Float | Number | float.class | Float.TYPE |
double | Double | Number | double.class | Double.TYPE |
char | Character | Object | char.class | Character.TYPE |
boolean | Boolean | Object | boolean.class | Boolean.TYPE |
一些關鍵源碼(解釋寫在註釋之中)
首先是 Integer 的範圍,與 int 相同,都爲 -2的31次方~2的31次方-1。
其中0x表示16進制,但是仔細一看,會發現最小值比最大值大一,這是因爲在二進制中負數是用補碼錶示的,最高位是符號位,0是正,1位負,所以正數最大值沒有問題,而負數最小值則根據首位1推出爲負數。
對於補碼不細講,只需記住
正數的補碼 = 原碼
負數的補碼 = 原碼符號位不變 + 數值位按位取反後+1
或者 = 原碼符號位不變 + 數值位從右邊數第一個1及其右邊的0保持不變,左邊安位取反
@Native public static final int MIN_VALUE = 0x80000000;
@Native public static final int MAX_VALUE = 0x7fffffff;
構造方法很簡單
public Integer(int value) {
this.value = value;
} // 給指定值創建對象
public Integer(String s) throws NumberFormatException {
this.value = parseInt(s, 10);
} // 通過字符串解析出值創建對象
裝箱方法
public static Integer valueOf(int i) {
// 如果值在low和high之間 low爲-128 high一般默認爲127
if (i >= IntegerCache.low && i <= IntegerCache.high)
// 則直接返回常量池中的對象
return IntegerCache.cache[i + (-IntegerCache.low)];
// 否則用new新建一個Integer對象返回
return new Integer(i);
}
在源碼中我們發現了一個靜態嵌套類IntegerCache,它擁有兩個 final 值 low 和 high,代表緩存的 Integer 的對象的範圍,這樣在裝箱時可以直接獲取,而不用再創建對象。
private static class IntegerCache {
static final int low = -128; // 緩存最小值
static final int high; // 緩存最大值
// low到high的Integer對象都被保存在這裏 裝箱時取
static final Integer cache[];
// 根據類加載機制 以下方法塊在類初始化時執行
// 會率先將low到high的Integer保存在cache數組中
static {
int h = 127;
/* 此處省略一些無關代碼 */
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
// 私有構造方法 保證類無法創建對象
// 因爲該類僅作爲事先緩存Integer對象
private IntegerCache() {}
}
再看toString()方法
public static String toString(int i) {
if (i == Integer.MIN_VALUE) //爲最小值則直接return
return "-2147483648";
// 計算數字的長度
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
// 根據長度創建字符數組
char[] buf = new char[size];
// 不斷獲取字符放入數組中
getChars(i, size, buf);
// 根據字符數組返回新字符串
return new String(buf, true);
}
裏面的 stringSize 方法很有趣,很多情況一般會用循環除10來計算長度,不衆所周知,JDK源碼是非常講究效率的,它直接保存了在 MAX 範圍內的所有每個長度最大值,直接依次比較,十分高效。
final static int [] sizeTable = { 9, 99, 999, 9999,
99999, 999999, 9999999,
99999999, 999999999, Integer.MAX_VALUE };
static int stringSize(int x) {
for (int i=0; ; i++)
if (x <= sizeTable[i])
return i+1;
}
總之對於我們來說,java 中的每一個基礎類我們都應把它學習透徹,理解它的執行原理,儘量對源碼中的關鍵方法都能掌握,這樣在我們的編程生涯中,才能做到遊刃有餘,不犯一些低級錯誤。
同時,jdk 的源碼都是十分優秀,高效的,在 jdk 版本不斷迭代的過程中,很多關鍵類的代碼都會進行優化和調整,確保程序的高效執行。
文章到這裏就結束啦,我還是大學生哦,喜歡學習的小夥伴可以評論交流,或者加關注,一起學習更輕鬆。
素質三連。。。