一、 簡單介紹
Java 是一門面向對象的編程語言,分爲:基本類型(primitive types)和引用類型(reference types),詳情見博客- 基本類型 。
1、關係
int 是我們經常使用的基礎數據類型,在我們的使用中經常出現,而Integer在我們的日常工作中也會出現,那麼他們的關係是什麼呢?
Integer 是 int 的包裝類,jdk 1.5以後添加了自動拆裝箱功能。也就是在編譯期可以將int 裝箱爲 Integer ,也可以將Integer 拆箱爲 int ,這裏涉及一個緩存的問題下面會講,
2、注意
雖然自動拆裝箱爲我們勉去了手動進行這些操作的麻煩,但是我們還是要儘量避免無意中的拆裝箱行爲,尤其是在對於性能很敏感的地方。【畢竟創建大量對象和創建大量整數的開銷不一樣,效率差距很大】。
二、 代碼層次理解
1、關於Number
所有基礎數據類型的包裝類都是繼承了Number父類
public final class Double extends Number
implements Comparable<Double> {}
- Number類代碼:
public abstract class Number implements java.io.Serializable {
public abstract int intValue();
public abstract long longValue();
public abstract float floatValue();
public abstract double doubleValue();
public byte byteValue() {
return (byte)intValue();
}
public short shortValue() {
return (short)intValue();
}
private static final long serialVersionUID = -8742448824652078965L;
}
所有的包裝類型都繼承了該類 ,該類又實現了序列化接口。
2、Integer
Integer 實現的時候 內部有成員變量 value 該 變量使用 private final 進行修飾 ,第一點 使用private 是防止我們主動的去獲取到內部的value 值,並進行修改,但是因爲java 提供了反射能力 ,所以我們還是可以獲取到 Integer 內部的 value 所以我們使用final 修飾 直接阻斷這一可能。
Integer 內部提供了緩存機制 就是如果我們獲取的整數在-128 -127 之間的 直接從緩存中取出。
private final int value;
- 使用valueOf()完成了緩存功能:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
如果值得範圍在-128 到 127 之間 就從緩衝中去 ,否則 創建新的Integr對象。
IntegerCache 源代碼:
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
3、其他
首先 原始數據類型不是線程安全的 ,如果使用原始數據我們需要結合其他手段纔可以達到線程安全比如: volitale,此外在我們需要有線程安全的場景時建議使用 : Atomic 原子類。【對於float、long這種比較寬的數據類型甚至會出現不能保證更新操作的原子性,可能出現程序讀取到了更新了一半的數值。】。
還有就是 原始數據類型不支持泛型,而泛型中使用的都是引用類型的數據。
原始類型創建 開銷小,而引用類型創建開銷大【因爲需要維護類型 等其他相關信息】。所以我們發現Integer 的緩存也是爲了提高效率 ,提升性能的因爲我們一般使用的數據都在-128 到127 之間,這樣不至於頻繁創建Integer對象 。
三、 深層次理解
1、談一下 對象的組成
對象由三部分組成:對象頭、對象實例、對其填充。
- 對象頭:
對象頭一般是16個字節,包括兩部分第一部分是:哈希嗎、鎖狀態標誌、線程持有的鎖、偏向鎖ID、GC 分代年齡等,第二部分是:類型指針【對象指向他屬於的類的信息】。
- 對象實例:
對象實例就是對象存儲的真正有效信息,也是程序中定義各種類型的字段包括父類繼承和子類定義的,這部分的存儲順序會被虛擬機和代碼中的定義順序影響【虛擬機的volatile 指令重排會影響】,
- 對其填充:
相當於佔位符的作用,因爲內存的使用需要是8字節的倍數。
知道了上面這些 ,我們就應該知道 創建一個對象需要存儲的數據是很多的,所以這就是爲什麼我們要儘量去避免使用包裝類型來替代基礎類型。
創建一萬個 整數 和創建一萬個對象,所佔用的存儲以及效率是有很大區別的。
2、總的談一下
因爲原始類型和Java泛型不能配合使用,所以Java設計了自動拆裝箱這個性能【其實也就是基礎類型和Object之間的轉換】,這樣避免了我們在使用的時候自己去顯示的進行轉換,
對於原始數據我們內存中存的就是一個數,獲取的時候直接獲取到的就是這個數本身,而對於引用類型 ,我們首先要去獲取這個對象的引用 ,然後通過引用地址去找到堆上具體的數,這樣多了一次IO,而IO 是很影響性能的,在加之對象存儲所佔用的空間也大,所以對象的性能 在計算的時候相較於基礎類型是要差許多的,所以爲了彌補引用類型在計算時性能差的問題 引入了緩存比如Integer 引入了 static valueOf() 的緩存機制,其他的如Boolean 、Char 等也都有相應的實現。
所以我們在進行計算的時候,最好能使用原始數據類型的話 儘量使用 原始數據類型, 而 對於我們需要使用泛型的時候 使用引用類型即可。