1.Integer的常量池
看下面一段代碼:
package cn.qlq.test; public class ArrayTest { public static void main(String[] args) { Integer i1 = new Integer(1); Integer i2 = new Integer(1); System.out.println(i1.hashCode()); System.out.println(i2.hashCode()); System.out.println(i1 == i2); System.out.println(i1.equals(i2)); System.out.println("-------------------"); Integer i3 = 1; Integer i4 = 1; System.out.println(i3.hashCode()); System.out.println(i4.hashCode()); System.out.println(i3 == i4); System.out.println(i3.equals(i4)); } }
1
1
false
true
-------------------
1
1
true
true
基本知識:我們知道,如果兩個引用指向同一個對象,用==表示它們是相等的。如果兩個引用指向不同的對象,用==表示它們是不相等的,即使它們的內容相同。
解釋:Integer i1 = new Integer(1)的時候是在Java堆中創建一個Integer對象,i1指向堆中的對象,i1與常量池沒關系,所以i1==i2為false。
Integer i3=1;的時候是從常量池中查找值為1的常量,i3指向該常量;Integer i4=1的時候會直接指向該常量,所以 i3 == i4為true。
這就是它有趣的地方了。如果你看去看 Integer.Java 類,你會發現有一個內部私有類,IntegerCache.java,它緩存了從-128到127之間的所有的整數對象。
所以事情就成了,所有的小整數在內部緩存,然后當我們聲明類似——
Integer bInteger=127;
它實際在內部的操作是:
Integer bInteger=Integer.valueOf(127);
現在,如果我們去看valueOf()方法,我們可以看到:
public static Integer valueOf(int i) { assert IntegerCache.high >= 127; if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
如果值的范圍在-128到127之間,它就從高速緩存返回實例。
所以…下面這兩個指向同一個對象:
Integer aInteger=127;
Integer bInteger=127;
我們可以得到true。
現在你可能會問,為什么這里需要緩存?
合乎邏輯的理由是,在此范圍內的“小”整數使用率比大整數要高,因此,使用相同的底層對象是有價值的,可以減少潛在的內存占用。
然而,通過反射API你會誤用此功能。
現在對代碼進行反編譯和反匯編查看:
package zd.dms.test; public class ArrayTest { public static void main(String[] args) { Integer i1 = 25; Integer i2 = new Integer(26); } }
反編譯:
package zd.dms.test; public class ArrayTest { public static void main(String[] paramArrayOfString) { Integer localInteger1 = Integer.valueOf(25); Integer localInteger2 = new Integer(26); } }
反匯編:
C:\Users\Administrator\Desktop>javap -c -v ArrayTest.class Classfile /C:/Users/Administrator/Desktop/ArrayTest.class Last modified 2018-9-3; size 384 bytes MD5 checksum 6535da703ea8fa15da765de7bb03300b Compiled from "ArrayTest.java" public class zd.dms.test.ArrayTest SourceFile: "ArrayTest.java" minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #6.#15 // java/lang/Object."<init>":()V #2 = Methodref #3.#16 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer; #3 = Class #17 // java/lang/Integer #4 = Methodref #3.#18 // java/lang/Integer."<init>":(I)V #5 = Class #19 // zd/dms/test/ArrayTest #6 = Class #20 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 main #12 = Utf8 ([Ljava/lang/String;)V #13 = Utf8 SourceFile #14 = Utf8 ArrayTest.java #15 = NameAndType #7:#8 // "<init>":()V #16 = NameAndType #21:#22 // valueOf:(I)Ljava/lang/Integer; #17 = Utf8 java/lang/Integer #18 = NameAndType #7:#23 // "<init>":(I)V #19 = Utf8 zd/dms/test/ArrayTest #20 = Utf8 java/lang/Object #21 = Utf8 valueOf #22 = Utf8 (I)Ljava/lang/Integer; #23 = Utf8 (I)V { public zd.dms.test.ArrayTest(); flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 3: 0 public static void main(java.lang.String[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=3, args_size=1 0: bipush 25 2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 5: astore_1 6: new #3 // class java/lang/Integer 9: dup 10: bipush 26 12: invokespecial #4 // Method java/lang/Integer."<init>":(I)V 15: astore_2 16: return LineNumberTable: line 6: 0 line 7: 6 line 8: 16 }
bipush 25 將25推至棧頂
invokestatic 調用Integer的靜態方法valueOf(int)方法
astore_1 將棧頂引用型數值存入第二個本地變量
new 調用new Integer(int)
dup 復制棧頂數值(數值不能是long或double類型的)並將復制值壓入棧頂
bipush 26 將26推至棧頂
invokespecial 調用Integer的初始化方法(init)
astore_2 將棧頂引用型數值存入第三個本地變量
return 返回,類型是void
補充:
aload_0 //將this引用推送至棧頂,即壓入棧。
總結:Integer i = value;如果i是在-128到127之間,不會去堆中創建對象,而是直接返回IntegerCache中的值;如果值不在上面范圍內則會從堆中創建對象。= 走的是valueOf()方法,valueOf(int)會走緩存。
Integer i2 = new Integer(xxxx);不管參數的value是多少都會從堆中創建對象,與IntegerCache沒關系。
2.String常量池問題:
package cn.qlq.test; public class ArrayTest { public static void main(String[] args) { String s1 = new String("1"); String s2 = new String("1"); System.out.println(s1.hashCode());// 49 System.out.println(s2.hashCode());// 49 System.out.println(s1 == s2);// false System.out.println(s1.equals(s2));// true System.out.println("-------------------"); String s3 = "1"; String s4 = "1"; System.out.println(s3 == s4);// true System.out.println(s3.equals(s4));// true System.out.println(s3.hashCode());// 49 System.out.println(s4.hashCode());// 49 } }
String的hashCode不是返回地址,是對其值進行遍歷運算。與地址沒關系,只對值計算,所以所有的hashCode一樣。
String s1 = new String("1"); 是在堆中創建一個String對象,並檢查常量池中是否有字面量為"1"的常量,沒有的話在常量區創建"1"並將堆中的對象指向該常量,有的話堆中的對象直接指向"1";
String s2 = new String("1"); 又在堆中創建一個String對象,並將s2指向該對象,其字面量"1"在前面已經創建,所以不會再創建常量區中創建字符串;
String s3 = "1"; 檢查常量池中有沒有字面量為"1"的字符串,如果沒有則創建並將s3指向該常量;有的話直接指向該該常量;
String s4 = "1" 的時候常量池已經有1,所以不會再創建對象,也就是s3與s4指向同一個對象。
所以我們可以用下面圖解解釋,String s = new String("xxx")在檢查常量池的時候會涉及到堆中創建對象;String s = "x"直接檢查常量池,不會涉及堆。
如下圖解:
一道經典的面試題:new String("abc")創建幾個對象?
簡單的回答是一個或者兩個,如果是常量區有值為"abc"的值,則只在堆中創建一個對象;如果常量區沒有則會在常量區創建"abc",此處的常量區是方法區的運行時常量池(也稱為動態常量區)。
我們需要明白只要是new都會在堆中創建對象。直接String s = "xxx"不會涉及堆,只在常量區檢查是否有該常量。