1. Integer類的基本信息
NOTE : 以JDK 1.8 爲準,補充部分屬性、方法在 JDK-1.6 / JDK-1.7中的變化說明;
- 繼承自: Number類
- 實現了:Serializable和Comparable接口
Integer類的結構和方法可以劃分爲以下區域:
- 類定義、屬性:7個屬性,都很好理解;
- 核心方法:構造Integer的方法和一些核心方法;
- int -> String方法:將int數字轉換各種進制的字符串 ;
- String -> int方法:將各種進制的字符串解析爲int數字 ;
- unsigned -> signed:java的int是有符號的,需要提供處理未符號的其他進制的方法;
- signed -> unsigned:將無符號的其他進制字符串轉換爲有符號的int數字;
2. 前提基礎
有符號和無符號
在計算機表中,正負數的表示可以使用有符號和無符號兩種表示,
- Java使用的是有符號的十進制表示方法,最高位爲符號位,所以表示範圍爲 - 2^31 到 2^31;
- 對於無符號表示並不是說無法表示負數,而是最高位也用來表示數本身,負數用“補碼”形式表示;
無符號表示可以翻閱很多資料:http://blog.csdn.net/sunweixiang1002/article/details/53048080
自動裝箱和拆卸
自動裝箱和拆卸是Java的語法糖,之所以需要分析Integer的源碼即因爲我們在日常編程中有很多自動裝箱和拆卸機制,這些會給我們帶來一些“坑”;
16進制語法糖
Java編程允許我們用Oxxxxx這樣的形式定義一個16進制數據,如 int x = Ox11 ,實際上x就是17,Java編譯時會將Ox11翻譯爲17,所以Integer的進制轉換方法,不需要提供類似 : toDeciamlInt(int i); 這樣的方法。
3. 核心方法 源碼分析
只看使用頻率最高的方法,其他的如 int -> String/ String -> Int 、有符號到無符號、進制轉換,這些不常用,有需要再看下源碼即可。
3.1 屬性
7個屬性,1個添加自1.8,主要是提升一些效率,避免運行時計算;
// 在Number中實現了Serializable接口
// 所以子類自動實現Serializable接口
public final class Integer extends Number implements Comparable<Integer> {
//最小值,表示 -2^31
//使用二進制分析就是最高位外後面31個0
@Native public static final int MIN_VALUE = 0x80000000;
//最大值,表示2^31 -1
@Native public static final int MAX_VALUE = 0x7fffffff;
//類型緩存
public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
// JDK 1.5添加,表示int 數字的位長度爲32位;
@Native public static final int SIZE = 32;
// JDK 1.8添加,計算好需要的字節長度,1字節8位,其實就是 4
public static final int BYTES = SIZE / Byte.SIZE;
//真正的值,final的不可以被動態改變
private final int value;
@Native
private static final long serialVersionUID = 1360826667806852920L;
}
3.2 常用的核心方法
之所以使用Integer對象,就是int作爲基本類型不具有對象方法,使用Integer對象頻率最高的即以下方法。
另外需要使用Integer對象的場景一般如下兩種:
- 需要使用一個特殊的值(null)表示無法計算,程序需要自己處理,比如從CacheUtil提供incry方法,如果key不存在則不計算,那麼可以返回Integer,如果key不存在返回null,業務判斷null則從其他數據源加載值來計算;
- 作爲Map的Key,這是最常用的場景,Map要求必須有hashCode和equals。
// 這裏定義了一個內部靜態類,用來緩存[-128,127]之間的Integer對象;
// 這個緩存是爲了JSL的自動裝箱機制提供的,可以通過VM參數:-XX:AutoBoxCacheMax=<size>來調整這個緩存範圍
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// cache的最大值可以通過屬性和vm參數設置
int h = 127;
// 去VM這個類查詢參數,看是否配置了AutoBoxCacheMax的值;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// 檢測配置的最大值不能超過 MAX_VALUE - 129;
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
}
}
//下面就是提前初始化,new出這些Integer對象出來
high = h;
// 緩存的最小值無法修改是:-128,
// 最大值不能超過 MAX_VALUE - 129 ,
// 如果high超過了,那麼數組分配的時候會拋出:NegativeArraySizeException
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
//斷言機制,JLS7 要求範圍必須大於[-128, 127]
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
//JDK 1.5提供的方法,優先使用valueOf方法替代new方法;
public static Integer valueOf(int i) {
// 先判斷是不是在緩存範圍內,內部類直接用屬性了。
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
//超出緩存範圍,直接new一個
return new Integer(i);
}
public static Integer valueOf(String s, int radix) throws NumberFormatException {
return Integer.valueOf(parseInt(s,radix));
}
public static Integer valueOf(String s) throws NumberFormatException {
return Integer.valueOf(parseInt(s, 10));
}
// 一般情況下不推薦使用
public Integer(int value) {
this.value = value;
}
public Integer(String s) throws NumberFormatException {
this.value = parseInt(s, 10);
}
// 縮小位數,會拋出高位內容
public byte byteValue() {
return (byte)value;
}
// 縮小位數,會拋棄高位內容
public short shortValue() {
return (short)value;
}
//非常常用的一個方法
public int intValue() {
return value;
}
//擴寬位數
public long longValue() {
return (long)value;
}
//擴寬位數
public float floatValue() {
return (float)value;
}
//擴寬位數
public double doubleValue() {
return (double)value;
}
//默認轉換爲10進制
public String toString() {
return toString(value);
}
/**
* 仔細看看這個方法,非常簡單,就是返回value值作爲hashCode.
* Integer.valueOf(1); -> 1;
* */
@Override
public int hashCode() {
return Integer.hashCode(value);
}
//JDK 1.8 抽象出來
public static int hashCode(int value) {
return value;
}
/**
* equals很熟悉,原理還是比較value是否== 但是這裏先判斷了是不是Integer的;
* 在代碼中 Integer x = 1; x.equasl(1L); 這是能編譯過去的,但是返回的是false;
* 這會引發一系列問題,尤其是作爲Map的Key,要注意,此類場景不常用,但是容易採坑;
*/
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
//JDK 1.2 提供的比較接口,用來排序
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
// 1.7 新增
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
核心方法不多,而且大部分都很簡單,核心方法中有一段IntegerCache,這是第重點,是爲了支持Java的autoboxing機制,提升自動裝箱和拆箱的效率;
3.2.1 自動裝箱和拆箱
Java的自動裝箱和拆卸其實是一種語法糖,比如如下代碼:
Integer x = 1;
// 編譯後實際上的寫法是:
Integer x = Integer.valuof(1);
3.2.2 == 比較的問題
對象的 == 判斷是判斷的引用,那麼Integer的 == 也不例外,但是自動裝箱可能破壞我們的認知,比如如下代碼:
Integer x = 1;
System.out.println(x == 1); // case 1-0 : true 自動拆箱
System.out.println(1 == x); // case 1-1 : true 自動拆箱
Integer y = 1000;
System.out.println(y == 1000); // case 2 : true 自動拆箱
System.out.println(y == new Integer(1000)); // case : false 沒有拆箱
根據源碼可以分析出:
- X == 1 這個不管是拆箱還是裝箱,== 都是true, 那麼實際上是被裝箱了還是拆箱了呢?
- y == new Integer(10000) ,因爲y被自動裝箱了,而且IntegerCache還沒有緩存,所以肯定是否false;
- y == 1000 這個就比較疑惑了,如果對1000自動裝箱,那麼就是false,如果對y自動拆箱那麼肯定就是true;
javap -c的反編譯內容如下:
Code:
## 這三個指令就是Integer x = Integer.valueOf(1);
0: iconst_1
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1
## 這幾條指令就是System.out.println(x == 1);
## 很明顯x == 1使用的是 x.intValue == 1
## 也就是自動拆箱了
5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
8: aload_1
9: invokevirtual #4 // Method java/lang/Integer.intValue:()I
12: iconst_1
13: if_icmpne 20
16: iconst_1
17: goto 21
20: iconst_0
21: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
## 這幾條指令就是System.out.println(1 == x);
## 這裏也證明了是自動拆箱了
24: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
27: iconst_1
28: aload_1
29: invokevirtual #4 // Method java/lang/Integer.intValue:()I
32: if_icmpne 39
35: iconst_1
36: goto 40
39: iconst_0
40: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
## 這裏1000沒有被預編譯到常量池
## 先自動裝箱 Integer y = Integer.valueOf(1000);
43: sipush 1000
46: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
49: astore_2
## 這裏自動拆箱了,System.out.println(y.intValue == 1000);
50: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
53: aload_2
54: invokevirtual #4 // Method java/lang/Integer.intValue:()I
57: sipush 1000
60: if_icmpne 67
63: iconst_1
64: goto 68
67: iconst_0
68: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
## 這裏編譯時沒有拆箱用比較的是integer的==
71: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
74: aload_2
75: new #6 // class java/lang/Integer
78: dup
79: sipush 1000
82: invokespecial #7 // Method java/lang/Integer."<init>":(I)V
85: if_acmpne 92
88: iconst_1
89: goto 93
92: iconst_0
93: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
96: return
NOTE : Java的編譯器在Integer和int比較的時候使用的是拆箱機制,Integer的比較要使用equals不要使用 == ,雖然這在一定範圍內可以被編譯器優化爲自動拆箱,並且因爲緩存的原因有時是正確的,但是實際上很危險。
3.2.3 valueOf的使用
Integer在構造的時候推薦使用valueOf而不是new Integer,valueOf會首先判斷是否需要緩存,也會造成如下的不同:
Integer x = 120;
Integer y = 120;
Integer x2 = 150;
Integer y2 = 150;
System.out.println(x == y);(case 1 : true)
System.out.println(x == new Integer(120)); (case 2 : false)
System.out.println(x == Integer.valueOf(120)); (case 2 : true)
System.out.println(x2 == y2) ; // case 4 : false