天天看點

java 類 比較大小_Java中包裝類型的大小比較

一、案例

我們先來看下這個例子:

public class IntegerTest {

public static void main(String[] args) {

Integer a = 127;

Integer b = 127;

System.out.println(a == b);

a = 128;

b = 128;

System.out.println(a == b);

a = -128;

b = -128;

System.out.println(a == b);

a = -129;

b = -129;

System.out.println(a == b);

}

}

程式運作結果:

true

false

true

false

看到這個結果,是不是很疑惑,不應該都是true嗎?

二、剖析

要弄懂這其中的緣由,我們要先明白上面的程式到底做了什麼?

javap是JDK自帶的反彙編器,可以檢視java編譯器為我們生成的位元組碼。通過它,我們可以對照源代碼和位元組碼,進而了解很多編譯器内部的工作。

于是,我們通過javap指令反編譯IntegerTest.class位元組碼檔案,得到結果如下:

$ javap -c IntegerTest

▒▒▒▒: ▒▒▒▒▒▒▒ļ▒IntegerTest▒▒▒▒com.lian.demo.IntegerTest

Compiled from "IntegerTest.java"

public class com.lian.demo.IntegerTest {

public com.lian.demo.IntegerTest();

Code:

0: aload_0

1: invokespecial #1 // Method java/lang/Object."": ()V

4: return

public static void main(java.lang.String[]);

Code:

0: bipush 127

2: invokestatic #2 // Method java/lang/Integer.valueOf: (I)Ljava/lang/Integer;

5: astore_1

6: bipush 127

8: invokestatic #2 // Method java/lang/Integer.valueOf: (I)Ljava/lang/Integer;

11: astore_2

12: getstatic #3 // Field java/lang/System.out:Ljava/ io/PrintStream;

15: aload_1

16: aload_2

17: if_acmpne 24

20: iconst_1

21: goto 25

24: iconst_0

25: invokevirtual #4 // Method java/io/PrintStream.printl n:(Z)V

28: sipush 128

31: invokestatic #2 // Method java/lang/Integer.valueOf: (I)Ljava/lang/Integer;

34: astore_1

35: sipush 128

38: invokestatic #2 // Method java/lang/Integer.valueOf: (I)Ljava/lang/Integer;

41: astore_2

42: getstatic #3 // Field java/lang/System.out:Ljava/ io/PrintStream;

45: aload_1

46: aload_2

47: if_acmpne 54

50: iconst_1

51: goto 55

54: iconst_0

55: invokevirtual #4 // Method java/io/PrintStream.printl n:(Z)V

58: bipush -128

60: invokestatic #2 // Method java/lang/Integer.valueOf: (I)Ljava/lang/Integer;

63: astore_1

64: bipush -128

66: invokestatic #2 // Method java/lang/Integer.valueOf: (I)Ljava/lang/Integer;

69: astore_2

70: getstatic #3 // Field java/lang/System.out:Ljava/ io/PrintStream;

73: aload_1

74: aload_2

75: if_acmpne 82

78: iconst_1

79: goto 83

82: iconst_0

83: invokevirtual #4 // Method java/io/PrintStream.printl n:(Z)V

86: sipush -129

89: invokestatic #2 // Method java/lang/Integer.valueOf: (I)Ljava/lang/Integer;

92: astore_1

93: sipush -129

96: invokestatic #2 // Method java/lang/Integer.valueOf: (I)Ljava/lang/Integer;

99: astore_2

100: getstatic #3 // Field java/lang/System.out:Ljava/ io/PrintStream;

103: aload_1

104: aload_2

105: if_acmpne 112

108: iconst_1

109: goto 113

112: iconst_0

113: invokevirtual #4 // Method java/io/PrintStream.printl n:(Z)V

116: return

}

标号2的code調用了靜态的valueOf方法解析Integer執行個體,也就是說Integer a = 127; 在編輯期進行了自動裝箱,即把基本資料類型轉換為包裝類型。

從JDK1.5就開始引入了自動拆裝箱的文法功能,也就是系統将自動進行基本資料類型和與之相對應的包裝類型之間的轉換,這使得程式員書寫代碼更加友善。

裝箱過程是通過調用包裝器的valueOf方法實作的。

拆箱過程是通過調用包裝器的xxxValue方法實作的(xxx表示對應的基本資料類型)。

當給a指派時,實際上是調用了Integer.valueOf(int i)方法。其JDK 8源碼如下:

public static Integer valueOf(int i) {

if (i >= IntegerCache.low && i <= IntegerCache.high)

return IntegerCache.cache[i + (-IntegerCache.low)];

return new Integer(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;

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() {}

}

看到了沒,當賦的基本資料類型值不在[-128, 127]之間,會去Java堆記憶體中new一個對象出來,顯然它們不是兩個不同的對象,是以結果false;

而值在[-128, 127]之間,會直接從IntegerCache中擷取,也就是從緩存中取值,不用再建立新的對象,即同一個對象,是以結果true。

關系操作符“==”生成的是一個boolean結果,它們計算的是操作數的值之間的關系。如果是基本類型則直接判斷其值是否相等,如果是對象則判斷是否是同一個對象的引用,即其引用變量所指向的對象的位址是否相同。

三、結論

在阿裡巴巴Java開發手冊中,它是這麼描述的:

【強制】所有的相同類型的包裝類對象之間值的比較,全部使用equals方法比較。

說明:對于Integer var = ? 在-128 至 127範圍内的指派,Integer對象是在IntegerCache.cache産生,會複用己有對象,這個區間内的Integer值可以直接使用==進行判斷,但是這個區間之外的所有資料,都會在堆上産生,并不會複用己有對象,這是一個大坑,推薦使用equals方法進行判斷。

可能你還會問,問啥是equals方法?這就要看equals方法到底做了什麼?可以參考《Java中關系操作符“==”和equals()方法的差別》

我們來看下Integer類中equals源碼:

public boolean equals(Object obj) {

if (obj instanceof Integer) {

return value == ((Integer)obj).intValue();

}

return false;

}

直接通過intValue()方法拆箱,即将包類型轉換為基本資料類型。是以equals方法比較的是它們的值了。

四、注意事項

包裝類型

緩存指派範圍

基本資料類型

二進制數

Boolean

全部緩存

boolean

1

Byte

[-128, 127]

byte

8

Character

<=127

char

16

Short

[-128, 127]

short

16

Integer

[-128, 127]

int

32

Long

[-128, 127]

long

64

Float

沒有緩存

float

32

Double

沒有緩存

double

64

是以兩個同類型的Float或Double類型的==比較永遠都是傳回false。