關(guān)于Java中你所不知道的Integer詳解
前言
本文主要給大家介紹了關(guān)于Java中Integer的相關(guān)內(nèi)容,分享出來供大家參考學(xué)習(xí),下面話不多說了,來一起看看詳細(xì)的介紹吧。
實(shí)參形參
前些天看到朋友圈分享了一片文章《Java函數(shù)的傳參機(jī)制——你真的了解嗎?》
有些觸發(fā),之前也研究過Java的Integer,所以寫下本文,希望對你有所幫助。
交換
首先來看一個示例。
請用Java完成swap函數(shù),交換兩個整數(shù)類型的值。
public static void test() throws Exception {
Integer a = 1, b = 2;
swap(a, b);
System.out.println("a=" + a + ", b=" + b);
}
static void swap(Integer a, Integer b){
// 需要實(shí)現(xiàn)的部分
}
第一次
如果你不了解Java對象在內(nèi)存中的分配方式,以及方法傳遞參數(shù)的形式,你有可能會寫出以下代碼。
public static void swapOne(Integer a, Integer b) throws Exception {
Integer aTempValue = a;
a = b;
b = aTempValue;
}
運(yùn)行的結(jié)果顯示a和b兩個值并沒有交換。
那么讓我們來看一下上述程序運(yùn)行時,Java對象在內(nèi)存中的分配方式:

對象地址分配
由此可以看到,在兩個方法的局部變量表中分別持有的是對a、b兩個對象實(shí)際數(shù)據(jù)地址的引用。
上面實(shí)現(xiàn)的swap函數(shù),僅僅交換了swap函數(shù)里局部變量a和局部變量b的引用,并沒有交換JVM堆中的實(shí)際數(shù)據(jù)。
所以main函數(shù)中的a、b引用的數(shù)據(jù)沒有發(fā)生交換,所以main函數(shù)中局部變量的a、b并不會發(fā)生變化。
那么要交換main函數(shù)中的數(shù)據(jù)要如何操作呢?
第二次
根據(jù)上面的實(shí)踐,可以考慮交換a和b在JVM堆上的數(shù)據(jù)值?
簡單了解一下Integer這個對象,它里面只有一個對象級int類型的value用以表示該對象的值。
所以我們使用反射來修改該值,代碼如下:
public static void swapTwo(Integer a1, Integer b1) throws Exception {
Field valueField = Integer.class.getDeclaredField("value");
valueField.setAccessible(true);
int tempAValue = valueField.getInt(a1);
valueField.setInt(a1, b1.intValue());
valueField.setInt(b1, tempAValue);
}
運(yùn)行結(jié)果,符合預(yù)期。
驚喜
上面的程序運(yùn)行成后,如果我在聲明一個Integer c = 1, d = 2;會有什么結(jié)果
示例程序如下:
public static void swapTwo(Integer a1, Integer b1) throws Exception {
Field valueField = Integer.class.getDeclaredField("value");
valueField.setAccessible(true);
int tempAValue = valueField.getInt(a1);
valueField.setInt(a1, b1.intValue());
valueField.setInt(b1, tempAValue);
}
public static void testThree() throws Exception {
Integer a = 1, b = 2;
swapTwo(a, b);
System.out.println("a=" + a + "; b=" + b);
Integer c = 1, d = 2;
System.out.println("c=" + c + "; d=" + d);
}
輸出的結(jié)果如下:
a=2; b=1 c=2; d=1
驚喜不驚喜!意外不意外!刺激不刺激!
深入
究竟發(fā)生了什么?讓我們來看一下反編譯后的代碼:
作者使用IDE工具,直接反編譯了這個.class文件
public static void testThree() throws Exception {
Integer a = Integer.valueOf(1);
Integer b = Integer.valueOf(2);
swapTwo(a, b);
System.out.println("a=" + a + "; b=" + b);
Integer c = Integer.valueOf(1);
Integer d = Integer.valueOf(2);
System.out.println("c=" + c + "; d=" + d);
}
在Java對原始類型int自動裝箱到Integer類型的過程中使用了Integer.valueOf(int)這個方法了。
肯定是這個方法在內(nèi)部封裝了一些操作,使得我們修改了Integer.value后,產(chǎn)生了全局影響。
所有這涉及該部分的代碼一次性粘完(PS:不拖拉的作者是個好碼農(nóng)):
public class Integer{
/**
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
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() {}
}
}
如上所示Integer內(nèi)部有一個私有靜態(tài)類IntegerCache,該類靜態(tài)初始化了一個包含了Integer.IntegerCache.low到java.lang.Integer.IntegerCache.high的Integer數(shù)組。
其中java.lang.Integer.IntegerCache.high的取值范圍在[127~Integer.MAX_VALUE - (-low) -1]之間。
在該區(qū)間內(nèi)所有的Integer.valueOf(int)函數(shù)返回的對象,是根據(jù)int值計算的偏移量,從數(shù)組Integer.IntegerCache.cache中獲取,對象是同一個,不會新建對象。
所以當(dāng)我們修改了Integer.valueOf(1)的value后,所有Integer.IntegerCache.cache[ 1 - IntegerCache.low ]的返回值都會變更。
我相信你們的智商應(yīng)該理解了,如果不理解請在評論區(qū)call 10086。
好了,那么不在[IntegerCache.low~IntegerCache.high)的部分呢?
很顯然,它們是幸運(yùn)的,沒有被IntegerCache緩存到,法外之民,每次它們的到來,都會new一邊,在JVM上分配一塊土(內(nèi))地(存)。
遐想
如果我把轉(zhuǎn)換的參數(shù)換成類型換成int呢?
public static void testOne() throws Exception {
int a = 1, b = 2;
swapOne(a, b);
System.out.println("a=" + a + ", b=" + b);
}
static void swapOne(int a, int b){
// 需要實(shí)現(xiàn)的部分
}
以作者目前的功力,無解。高手可以公眾號留言,萬分感謝!
至此swap部分已經(jīng)講完了。
1 + 1
首先讓我們來看一下代碼:
public static void testOne() {
int one = 1;
int two = one + one;
System.out.printf("Two=%d", two);
}
請問輸出是什么?
如果你肯定的說是2,那么你上面是白學(xué)了,請直接撥打95169。
我可以肯定的告訴你,它可以是[Integer.MIN_VALUE~Integer.MAX_VALUE]區(qū)間的任意一個值。
驚喜不驚喜!意外不意外!刺激不刺激!
讓我們再擼(捋)一(一)串(遍)燒(代)烤(碼)。
作者使用IDE工具,直接反編譯了這個.class文件
public static void testOne() {
int one = 1;
int two = one + one;
System.out.printf("Two=%d", two);
}
這里的變量two竟然沒有調(diào)用Integer.valueOf(int) ,跟想象的不太一樣,我懷疑這是IDE的鍋。
所以果斷查看編譯后的字節(jié)碼。以下為摘錄的部分字節(jié)碼:
LDC "Two=%d" ICONST_1 ANEWARRAY java/lang/Object DUP ICONST_0 ILOAD 2 INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; AASTORE INVOKEVIRTUAL java/io/PrintStream.printf (Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; POP
可以看出確實(shí)是IDE的鍋,這里不僅調(diào)用了一次Integer.valueOf(int) ,而且還創(chuàng)建一個Object的數(shù)組。
完整的Java代碼應(yīng)該是如下所示:
public static void testOne() {
int one = 1;
int two = one + one;
Object[] params = { Integer.valueOf(two) };
System.out.printf("Two=%d", params);
}
所以只要在方法調(diào)用前修改Integer.IntegerCache.cache[2+128]的值就可以了,所以在類的靜態(tài)初始化部分加些代碼。
public class OnePlusOne {
static {
try {
Class<?> cacheClazz = Class.forName("java.lang.Integer$IntegerCache");
Field cacheField = cacheClazz.getDeclaredField("cache");
cacheField.setAccessible(true);
Integer[] cache = (Integer[]) cacheField.get(null);
//這里修改為 1 + 1 = 3
cache[2 + 128] = new Integer(3);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void testOne() {
int one = 1;
int two = one + one;
System.out.printf("Two=%d", two);
}
}
two == 2 ?
在修改完Integer.IntegerCache.cache[2 + 128]的值后,變量two還等于2么?
public static void testTwo() {
int one = 1;
int two = one + one;
System.out.println(two == 2);
System.out.println(Integer.valueOf(two) == 2);
}
上述代碼輸出如下
true false
因?yàn)閠wo == 2不涉及到Integer裝箱的轉(zhuǎn)換,還是原始類型的比較,所以原始類型的2永遠(yuǎn)等于2。
Integer.valueOf(two)==2的真實(shí)形式是Integer.valueOf(two).intValue == 2 ,即3==2,所以是false。
這里可以看到如果拿一個值為null的Integer變量和一個int變量用雙等號比較,會拋出NullPointException。
這里的方法如果換成System.out.println("Two=" + two)的形式會有怎樣的輸出?你可以嘗試一下。
后記
XCache
| 類 | 是否有Cache | 最小值 | 最大值 |
|---|---|---|---|
| Boolean | 無 | -- | -- |
| Byte | ByteCache | -128 | 127(固定) |
| Short | ShortCache | -128 | 127(固定) |
| Character | CharacterCache | 0 | 127(固定) |
| Integer | IntegerCache | -128 | java.lang.Integer.IntegerCache.high |
| Long | LongCache | -128 | 127(固定) |
| Float | 無 | -- | -- |
| Double | 無 | -- | -- |
java.lang.Integer.IntegerCache.high
看了IntegerCache類獲取high的方法sun.misc.VM.getSavedProperty,可能大家會有以下疑問,我們不拖沓,采用一個問題一解答的方式。
1. 這個值如何如何傳遞到JVM中?
和系統(tǒng)屬性一樣在JVM啟動時,通過設(shè)置-Djava.lang.Integer.IntegerCache.high=xxx傳遞進(jìn)來。
2. 這個方法和System.getProperty有什么區(qū)別?
為了將JVM系統(tǒng)所需要的參數(shù)和用戶使用的參數(shù)區(qū)別開,
java.lang.System.initializeSystemClass在啟動時,會將啟動參數(shù)保存在兩個地方:
2.1 sun.misc.VM.savedProps中保存全部JVM接收的系統(tǒng)參數(shù)。
JVM會在啟動時,調(diào)用java.lang.System.initializeSystemClass方法,初始化該屬性。
同時也會調(diào)用sun.misc.VM.saveAndRemoveProperties方法,從java.lang.System.props中刪除以下屬性:
- sun.nio.MaxDirectMemorySize
- sun.nio.PageAlignDirectMemory
- sun.lang.ClassLoader.allowArraySyntax
- java.lang.Integer.IntegerCache.high
- sun.zip.disableMemoryMapping
- sun.java.launcher.diag
以上羅列的屬性都是JVM啟動需要設(shè)置的系統(tǒng)參數(shù),所以為了安全考慮和隔離角度考慮,將其從用戶可訪問的System.props分開。
2.2 java.lang.System.props中保存除了以下JVM啟動需要的參數(shù)外的其他參數(shù)。
- sun.nio.MaxDirectMemorySize
- sun.nio.PageAlignDirectMemory
- sun.lang.ClassLoader.allowArraySyntax
- java.lang.Integer.IntegerCache.high
- sun.zip.disableMemoryMapping
- sun.java.launcher.diag
PS:作者使用的JDK 1.8.0_91
Java 9的IntegerCache
幻想一下,如果以上淘氣的玩法出現(xiàn)在第三方的依賴包中,絕對有一批程序員會瘋掉(請不要嘗試這么惡劣的玩法,后果很嚴(yán)重)。
慶幸的是Java 9對此進(jìn)行了限制。可以在相應(yīng)的module中編寫module-info.java文件,限制了使用反射來訪問成員等,按照需要聲明后,代碼只能訪問字段、方法和其他用反射能訪問的信息,只有當(dāng)類在相同的模塊中,或者模塊打開了包用于反射方式訪問。詳細(xì)內(nèi)容可參考一下文章:
在 Java 9 里對 IntegerCache 進(jìn)行修改?
感謝Lydia和飛鳥的寶貴建議和辛苦校對。
最后跟大家分享一個java中Integer值比較不注意的問題:
先來看一個代碼片段:
public static void main(String[] args) {
Integer a1 = Integer.valueOf(60); //danielinbiti
Integer b1 = 60;
System.out.println("1:="+(a1 == b1));
Integer a2 = 60;
Integer b2 = 60;
System.out.println("2:="+(a2 == b2));
Integer a3 = new Integer(60);
Integer b3 = 60;
System.out.println("3:="+(a3 == b3));
Integer a4 = 129;
Integer b4 = 129;
System.out.println("4:="+(a4 == b4));
}
這段代碼的比較結(jié)果,如果沒有執(zhí)行不知道各位心中的答案都是什么。
要知道這個答案,就涉及到Java緩沖區(qū)和堆的問題。
java中Integer類型對于-128-127之間的數(shù)是緩沖區(qū)取的,所以用等號比較是一致的。但對于不在這區(qū)間的數(shù)字是在堆中new出來的。所以地址空間不一樣,也就不相等。
Integer b3=60 ,這是一個裝箱過程也就是Integer b3=Integer.valueOf(60)
所以,以后碰到Integer比較值是否相等需要用intValue()
對于Double沒有緩沖區(qū)。
答案
1:=true
2:=true
3:=false
4:=false
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
多個sheet Excel 數(shù)據(jù)導(dǎo)入數(shù)據(jù)庫的實(shí)現(xiàn)方法
這篇文章主要介紹了多個sheet Excel 數(shù)據(jù)導(dǎo)入數(shù)據(jù)庫的實(shí)現(xiàn)方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03
Java 利用dom方式讀取、創(chuàng)建xml詳解及實(shí)例代碼
這篇文章主要介紹了Java 利用dom方式讀取、創(chuàng)建xml的相關(guān)資料,需要的朋友可以參考下2017-03-03
Java中HashMap和TreeMap的區(qū)別深入理解
首先介紹一下什么是Map。在數(shù)組中我們是通過數(shù)組下標(biāo)來對其內(nèi)容索引的,而在Map中我們通過對象來對對象進(jìn)行索引,用來索引的對象叫做key,其對應(yīng)的對象叫做value2012-12-12
Thread線程的基礎(chǔ)知識及常見疑惑點(diǎn)總結(jié)
在本篇內(nèi)容里小編給大家分享的是關(guān)于Thread線程的基礎(chǔ)知識及常見疑惑點(diǎn),對此有學(xué)習(xí)需求的朋友們可以學(xué)習(xí)參考下。2019-05-05

