從 Int 到 Integer 對象,細細品來還是有不少東西
- 2019 年 10 月 3 日
- 筆記
int 是 Java 八大原始類型之一,是 Java 語言中為數不多不是對象的東西,Integer 是 int 的包裝類,裏面使用了一個 int 類型的變量來存儲數據,提供了一些整數之間的常用操作,常規性的介紹就這麼一點,程序員不喜歡說,程序員就喜歡源碼,我們還是來看源碼吧
* @author Lee Boynton * @author Arthur van Hoff * @author Josh Bloch * @author Joseph D. Darcy * @since JDK1.0 */ public final class Integer extends Number implements Comparable<Integer> { /** * A constant holding the minimum value an {@code int} can * have, -2<sup>31</sup>. */ @Native public static final int MIN_VALUE = 0x80000000; /** * A constant holding the maximum value an {@code int} can * have, 2<sup>31</sup>-1. */ @Native public static final int MAX_VALUE = 0x7fffffff; /** * 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 = value; } /** * Constructs a newly allocated {@code Integer} object that * represents the {@code int} value indicated by the * {@code String} parameter. The string is converted to an * {@code int} value in exactly the manner used by the * {@code parseInt} method for radix 10. * * @param s the {@code String} to be converted to an * {@code Integer}. * @exception NumberFormatException if the {@code String} does not * contain a parsable integer. * @see java.lang.Integer#parseInt(java.lang.String, int) */ public Integer(String s) throws NumberFormatException { this.value = parseInt(s, 10); }
上面這段源碼是我截取出來的,在 Integer 類中,這些代碼不是連在一起的,把他們放在一起,那是因為我想說明點事情,我們仔細看看這段代碼,Integer 類是被 final ,這說明了什麼?用於存放變量的 value 也被 private final 修飾,這又說明了什麼?看着這些是不是有點熟悉呢? 沒錯,String 對象也是這樣的,這說明 Integer 對象也是不可變的,所以以後如果被問到 Integer 對象是不是不可變對象時,記得回答是喔。為什麼 Integer 對象也會設計成不可變對象呢?其實我也不知道,我沒有從文檔中找到答案,但是在楊曉峰老師的文章中看到過有關說明,楊曉峰老師說 Integer 類設計成不可變跟 getInteger() 方法有關係,getInteger()方法的源碼如下:
public static Integer getInteger(String nm, Integer val) { String v = null; try { v = System.getProperty(nm); } catch (IllegalArgumentException | NullPointerException e) { } if (v != null) { try { return Integer.decode(v); } catch (NumberFormatException e) { } } return val; }
getInteger() 方法是用來獲取系統屬性的,我們通過屬性來設置服務器的某個服務器的端口,如果 Integer 可變的話,那麼我們就能夠輕易的改變這個屬性的值,這會使得我們的產品存在安全風險。
上面我們我么簡單的聊了一下 Integer 類的實現,聊到 int 與 Integer,自然就少不了自動裝箱和自動拆箱。
1、自動裝箱、拆箱
自動裝箱和拆箱是從 JDK 1.5 開始引進的功能,它是一種語法糖,Java 可以根據上下文,自動的在原始類型和包裝類型中進行轉換,簡單的來說就是 Java 平台保證了不同的寫法通過編譯之後會產生相同的位元組碼,保證了運行時是等價的。自動裝箱和拆箱極大地簡化了相關編程。
自動裝箱:將原始類型轉化為包裝類型的過程
比如將 int 類型轉換成 integer 類型,這就是原始類型到包裝類型的轉變,在編譯的時候,編譯器就會幫我們做自動轉換,這個過程對我們程序員來說是無感知的,例如這段給 Integer 對象賦值的代碼:
Integer x = 3000;
這段代碼經過編譯器之後,會轉換成下面這段代碼:
Integer x = Integer.valueOf(3000);
這就是自動裝箱過程,這個過程你是不知道的,所以它才叫自動裝箱,在自動裝箱的過程中使用到了 valueOf() 方法,來看看 JDK 中這個方法的源碼:
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
這個方法里,前面先進行了一個緩存判斷,如果你不知道的話,先忽略掉它,最後返回了 new Integer(i) 對象引用,這個方法就是幫你去調用了 Integer 類的構造器。這就是自動裝箱。
自動拆箱:將包裝類型轉換成原始類型的過程
將 Integer 類型轉換為 Int 類型,這是一個包裝類型轉成成原始類型的過程,在這個過程中就會涉及到自動拆箱。來看看這段代碼(這是一段很操蛋的代碼,實際中應該沒人這樣寫):
Integer mm = 1000; int mmm = mm;
在編譯的時候,這段代碼會被編譯器編譯成下面這段代碼:
Integer mm = Integer.valueOf(1000); int mmm = mm.intValue();
主要看int mmm = mm.intValue();
這行代碼,這行代碼跟我們寫的不一樣了,使用到了一個 intValue() 方法,來看看 Integer 類中 intValue() 方法的源碼:
/** * Returns the value of this {@code Integer} as an * {@code int}. */ public int intValue() { return value; }
這個方法的作用就是把 Integer 對象中用來存儲值的 value 變量返回了,這就是自動拆箱,好了,關於自動裝箱和自動拆箱我們都了解了,還記得自動裝箱過程中涉及到的緩存嗎?接下來我們一起了解一下。
2、Integer 緩存策略
在自動裝箱的 valueOf() 方法中,我們看到了有一個緩存判斷的操作,是的,Integer 類中有緩存池,會將使用頻繁的值緩存起來,以便提高系統的使用性能,在自動裝箱的過程中,會先判斷該值是否存在緩存池中,如果存在直接從緩存池中取出引用返回,如果不存在則調用構造函數構造對象。緩存是自動裝箱操作獨享的,直接通過構造函數構造出來的 Integer 對象即使值在緩存範圍內,也不會使用到緩存池。在 Integer 類中,使用了一個內部類來實現緩存,這個內部類叫做 IntegerCache,IntegerCache 類的源代碼如下:
/** * Cache to support the object identity semantics of autoboxing for values between * -128 and 127 (inclusive) as required by JLS. * * The cache is initialized on first usage. The size of the cache * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option. * During VM initialization, java.lang.Integer.IntegerCache.high property * may be set and saved in the private system properties in the * sun.misc.VM class. */ 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() {} }
從源碼和 Java 注釋中我們可以看出 IntegerCache 的緩存默認值範圍 -128 ~ 127 。但是我們也可以在啟動時通過 JVM 命令來設置緩存範圍的最大值,只需要在啟動時添加 -XX:AutoBoxCacheMax=
參數就可以了,但是記得這個 size 可不要亂設置,需要全方位考慮,比如你設置成 10 萬,那麼這 10 萬個數都會在啟動剛啟動時就添加到內存中,想想這會佔用你多少內存?這樣做就得不償失了,Java 公司設置成 -128 ~ 127 是有道理的,發現大部分人使用的值都在 -128 ~ 127 之間,這些值佔用的內存比較少,性能上比通過構造函數構造對象要好不少。如何你使用的 Integer 的值在緩存範圍的話,就用 Integer i = value 的形式構建對象,如果你的值不在緩存範圍內,則使用 Integer i = new Integer(value) 的形式構建 Integer 對象,避免自動裝箱的過程。最後我們來看一下 Integer 對象比較常用的方法 parseInt 方法
3、parseInt() 方法
parseInt() 方法的作用是用來將整數型的字符串轉換成整數,parseInt 方法需要和 valueOf 方法區分開來,有不少人會問這兩方法有什麼區別,最後度會返回 int 類型,都能將整數型的字符串轉換成整數型,比如這段代碼
System.out.println(Integer.parseInt("+12")); System.out.println(Integer.valueOf("+12"));
最後都會輸出 12 ,輸出的結果相同是因為 valueOf 方法使用中會調用 parseInt 方法將整數型字符轉換為整數,並且會在內存中創建一個值為 12 的 Integer 對象,然後返回這個對象引用。而 parseInt 方法只會幫你將整數型字符轉換為整數,不會額外的創建對象。所以它們兩得到相同的結果純屬是巧合。一起瞅瞅 parseInt 源代碼:
public static int parseInt(String s, int radix) throws NumberFormatException { /* * WARNING: This method may be invoked early during VM initialization * before IntegerCache is initialized. Care must be taken to not use * the valueOf method. */ if (s == null) { throw new NumberFormatException("null"); } if (radix < Character.MIN_RADIX) { throw new NumberFormatException("radix " + radix + " less than Character.MIN_RADIX"); } if (radix > Character.MAX_RADIX) { throw new NumberFormatException("radix " + radix + " greater than Character.MAX_RADIX"); } int result = 0; boolean negative = false; int i = 0, len = s.length(); int limit = -Integer.MAX_VALUE; int multmin; int digit; if (len > 0) { char firstChar = s.charAt(0); if (firstChar < '0') { // Possible leading "+" or "-" if (firstChar == '-') { negative = true; limit = Integer.MIN_VALUE; } else if (firstChar != '+') throw NumberFormatException.forInputString(s); if (len == 1) // Cannot have lone "+" or "-" throw NumberFormatException.forInputString(s); i++; } multmin = limit / radix; while (i < len) { // Accumulating negatively avoids surprises near MAX_VALUE digit = Character.digit(s.charAt(i++),radix); if (digit < 0) { throw NumberFormatException.forInputString(s); } if (result < multmin) { throw NumberFormatException.forInputString(s); } result *= radix; if (result < limit + digit) { throw NumberFormatException.forInputString(s); } result -= digit; } } else { throw NumberFormatException.forInputString(s); } return negative ? result : -result; }
在調用 parseInt 方法時,我們可以傳入一個 radix 變量,用來告訴它使用什麼進制來進行轉換,默認使用的是十進制。
文章不足之處,望大家多多指點,共同學習,共同進步
最後
打個小廣告,歡迎掃碼關注微信公眾號:「平頭哥的技術博文」,一起進步吧。