面試題:new Integer(112)和Integer.valueOf(112)的區別
面試官考察點猜想
這道題,考察的是對Integer這個物件原理的理解,關于這道題的變體有很多,我們會一一進行分析,
理解這道題,對于實際開發程序中防止出現意想不到的Bug很有用,建議大家認真思考和解讀,
背景知識詳解
關于Integer的實作
Integer是int的一個封裝類,它的構造實作如下,
/**
* The value of the {@code Integer}.
*
* @serial
*/
private final int value;
/**
* Constructs a newly allocated {@code Integer} object that
* represents the specified {@code int} value.
*
* @param value the value to be represented by the
* {@code Integer} object.
*/
public Integer(int value) {
this.value = https://www.cnblogs.com/mic112/archive/2021/10/30/value;
}
Integer中定義了一個int型別的value屬性,由于該屬性是final型別,因此需要通過構造方法來賦值,這個邏輯非常簡單,沒有太多要關注得,
結論: 當通過
new關鍵字構建一個Integer實體時,和所有普通物件的實體化相同,都是在堆記憶體地址中分配一塊空間,
Integer.valueOf
Integer.valueOf方法,是把一個字串轉換為Integer型別,該方法定義如下
public static Integer valueOf(String s) throws NumberFormatException {
return Integer.valueOf(parseInt(s, 10));
}
這個方法呼叫另外一個多載方法,該方法定義如下,
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
從這段代碼中發現,如果i的值是在IntegerCache.low和IntegerCache.high這個區間范圍,則通過下面這段代碼回傳Integer物件實體,
IntegerCache.cache[i+(-IntegerCache.low)];
否則,使用new Integer(i)創建一個新的實體物件,
IntegerCache是什么?
從它的命名來看,不難猜出它應該和快取有關系,簡單猜測就是:如果i的值在某個區間范圍內,則直接從快取中獲取物件,
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;
//high的值允許通過系統屬性來調整
String integerCacheHighPropValue =
https://www.cnblogs.com/mic112/archive/2021/10/30/sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
//如果配置了high的屬性值,則取兩者中最大的一個值作為IntegerCache的最高區間值,
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() {}
}
上述代碼的實作邏輯非常簡單:
- IntegerCache的取值區間為: IntegerCache.low=-128, IntegerCache.hign=127,其中
hign是可以通過系統引數來調整, - 創建一個Integer陣列,回圈初始化這個區間中的每一個值,
Integer為什么這么設計? 但凡涉及到Cache的,一定和性能有關,在Integer這個物件中,常用的數值區間是在-128到127之間,所以為了避免對這個區間范圍內的資料頻繁創建和銷毀物件,所以構建了一個快取,意味著后續只要不是通過
new關鍵字創建的Integer實體,在這個區間內的數值都會從IntegerCache中獲取,

問題解答
面試題:new Integer(112)和Integer.valueOf(112)的區別
理解了上面的原理后,再來解答這個問題就很容易了,
-
new Integer,是創建一個Integer物件實體, -
Integer.valueOf(112),Integer默認提供了Cache機制,在-128到127區間范圍內的資料,通過valueOf方法不需要創建新的物件實體,只需要從快取中獲取即可,
問題總結
Integer這個物件的變形面試題比較多,其中一個面試題比較典型,
有兩個Integer變數a,b,通過swap方法之后,交換a,b的值,請寫出swap的方法,
public class SwapExample {
public static void main(String[] args){
Integer a=1;
Integer b=2;
System.out.println("交換前:a="+a+",b="+b);
swap(a,b);
System.out.println("交換后:a="+a+",b="+b);
}
private static void swap(Integer a,Integer b){
//doSomething
}
}
基礎不是很好的同學,可能會很直接的按照”正確的邏輯“來撰寫程式,可能的代碼如下,
private static void swap(Integer a,Integer b){
Integer temp=a;
a=b;
b=temp;
}
程式邏輯,理論上是沒問題,定義一個臨時變數存盤a的值,然后再對a和b進行交換,而實際運行結果如下
交換前:a=1,b=2
交換后:a=1,b=2
Integer物件的重新賦值思考
Integer作為封裝物件型別,通過函式傳遞該參考以后,理論上來說,main方法中定義的a和b,以及傳遞到swap方法中的a、和b,指向同一個記憶體地址,那么按照上述代碼的實作,理論上來說也是成立的,

Java中有兩種引數傳遞型別,
- 值傳遞,傳遞的是資料的副本,方法執行中形式引數值的改變不影響實際引數的值,
- 參考傳遞,傳遞的是記憶體地址的參考,在方法執行中,由于參考物件的地址指向同一塊記憶體,所以對于物件資料的修改,會影響到參考了該地址的變數,
這么設計的好處,是為了減少記憶體的占用,提升訪問效率和性能,
那么Integer作為封裝型別,為什么傳遞的是副本,而不是參考呢?
我們來看一下Integer中value值得定義,可以發現該屬性是final修飾,意味著是不可更改,
/**
* The value of the {@code Integer}.
*
* @serial
*/
private final int value;
結論:在Java中,只有一種引數傳遞方式,就是值傳遞,但是,當引數傳的是基本型別時,傳的是值的拷貝,對拷貝變數的修改不影響原變數;當傳的是參考型別時,傳的是參考地址的拷貝,但是拷貝的地址和真實地址指向的都是同一個真實資料,因此可以修改原變數中的值;當傳的是Integer型別時,雖然拷貝的也是參考地址,指向的是同一個資料,但是Integer的值不能被修改,因此無法修改原變數中的值,
因此,上述代碼之所以沒有交換成功,是因為傳遞到swap方法中的a和b,會創建一個變數副本,這個副本中的值雖然發生了交換,但不影響原始值,

了解了這塊知識之后,我們的問題就變成了,如何對一個修飾了final關鍵字的屬性進行資料修改,那就是通過反射來實作,實作代碼如下.
public class SwapExample {
public static void main(String[] args){
Integer a=1;
Integer b=2;
System.out.println("交換前:a="+a+",b="+b);
swap(a,b);
System.out.println("交換后:a="+a+",b="+b);
}
private static void swap(Integer a,Integer b){
try {
Field field=Integer.class.getDeclaredField("value");
Integer temp= a;
field.setAccessible(true); //針對private修飾的變數,需要通過該方法設定,
field.set(a,b);
field.set(b,temp);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
那這段代碼運行完是否能達到預期呢? 上述程式運行結果如下:
交換前:a=1,b=2
交換后:a=2,b=2
從結果來看,確實是發生了變化,但是變化并不完整,因為b=1這個預期值并沒有出現,為什么呢?其實還是和今天分享得主題有關系,我們來逐步看一下,
Integer temp=a這個地方,基于IntegerCache的原理,這里并不會產生一個新的temp實體,意味著temp變數和a變數指向的記憶體地址是同一個,- 當通過
field.set方法,把a記憶體地址的值通過反射修改成b以后,那么此時a的值應該是2,注意:由于記憶體地址的值變成了2,而temp這個變數又指向該記憶體地址,因此temp的值自然就變成了2. - 接著使用
filed.set(b,temp)修改b屬性的值,此時temp的值時2,所以得到的結果b也變成了2.
private static void swap(Integer a,Integer b){
try {
Field field=Integer.class.getDeclaredField("value");
Integer temp= a;
field.setAccessible(true); //針對private修飾的變數,需要通過該方法設定,
field.set(a,b);
field.set(b,temp);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
理解了原理后,我們只需要修改Integer temp=a這段代碼,改成下面這種寫法,保證temp變數是一個獨立的實體,
Integer temp=new Integer(a);
修改以后運行結果如下
交換前:a=1,b=2
交換后:a=2,b=1
Mic說: 只有基本功足夠扎實,才能對任何問題的本質一眼看透,解決這些問題的時候也能得心應手,
關注[跟著Mic學架構]公眾號,獲取更多精品原創

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/342104.html
標籤:其他
上一篇:Python 學習筆記(五)
