Java 常量池
阿新 • • 發佈:2018-04-23
之間 等於 con 分配 計算 prope 解析 base null
最近在網上看到一些Android的面試題,關於String和Integer常量池的,就總結了一下,暫時先記錄下來,以後說不定能用到
1 public class JavaBase { 2 3 public static final String STRING20; // 常量 4 public static final String STRING21; // 常量 5 static { 6 STRING20 = "hello"; 7 STRING21 = "World"; 8 }9 10 public static void main(String[] args) { 11 12 Integer mInteger1 = new Integer("3"); 13 Integer mInteger2 = new Integer("3"); 14 System.out.println(mInteger1 == mInteger2);// false 創建兩個對象 15 16 // 對象無法與數值進行比較,所以對象會自動拆箱變成數值在進行比較 17 intmInteger3 = new Integer("3"); 18 Integer mInteger4 = new Integer("3"); 19 System.out.println(mInteger3 == mInteger4);// true 20 21 // 首先mInteger6 == (mInteger7+mInteger5),因為+這個操作符不適用於Integer對象,mInteger7 22 // 和mInteger5進行自動拆箱操作,進行數值相加,即mInteger6 ==3。然後Integer對象無法與數值進行直接比較,23 // 所以mInteger6自動拆箱轉為int值3,最終轉為3 ==3進行數值比較 24 Integer mInteger5 = new Integer(0); 25 Integer mInteger6 = new Integer(3); 26 Integer mInteger7 = new Integer(3); 27 System.out.println(mInteger6 == (mInteger7 + mInteger5));// true 在棧中計算 28 29 Integer mInteger8 = new Integer(3); 30 Integer mInteger9 = 3; 31 System.out.println(mInteger8 == mInteger9);// false 一個在棧中一個在堆中 32 33 Integer mInteger10 = 3; 34 Integer mInteger11 = 3; 35 System.out.println(mInteger10 == mInteger11);// true 實現了常量池 36 37 // 除Float和Double以外, 其它六種都實現了常量池,但是它們只在大於等於-128並且小於等於127時才使用常量池。 38 Double mDouble0 = 3d; 39 Double mDouble1 = 3d; 40 System.out.println(mDouble0 == mDouble1);// false 沒有實現常量池,相當於分別new一個 41 42 Integer mInteger12 = 400; 43 Integer mInteger13 = 400; 44 System.out.println(mInteger12 == mInteger13);// false大於127則在堆中創建,相當於new一個 45 46 // Boolean類也實現了常量池技術 47 Boolean bool1 = true; 48 Boolean bool2 = true; 49 System.out.println(bool1 == bool2);// 輸出true 50 51 Boolean bool3 = true; 52 Boolean bool4 = new Boolean(true); 53 System.out.println(bool3 == bool4);// 輸出false 一個在常量池中一個在堆中 54 55 // JVM對於字符串常量的"+"連接優化為連接後的值,"hello" + "World"經編譯器優化後就已經是helloWorld,在編譯期 56 // 字符串常量的值就確定下來。而對於字符串引用,由於在字符串的"+"連接中,有字符串引用存在,而引用的值在程序編譯期是無法 57 // 確定的,所以string0 +"World"無法被編譯器優化,只有在程序運行期來動態分配並將連接後的新地址賦給string1。 58 /** 59 * String string2 = "hello" + "World"會查找常量池中時候存在內容為"helloWorld"字符串對象,如存在則 60 * 直接讓string2引用該對象, 61 */ 62 String string0 = "hello"; 63 String string1 = string0 + "World"; 64 String string2 = "hello" + "World"; 65 System.out.println(string1 == "helloWorld"); // false 66 System.out.println(string2 == "helloWorld"); // true 67 System.out.println(string1 == string2); // false 68 69 /** 70 * String str = "hello"創建對象的過程 71 *1 首先在常量池中查找是否存在內容為"hello"字符串對象 72 *2 如果不存在則在常量池中創建"hello",並讓str引用該對象 73 *3 如果存在則直接讓str引用該對象 74 * 75 *String str = new String("hello")創建實例的過程 76 *1 首先在堆中(不是常量池)創建一個指定的對象"hello",並讓str引用指向該對象 77 *2 在字符串常量池中查看,是否存在內容為"hello"字符串對象 78 *3 若存在,則將new出來的字符串對象與字符串常量池中的對象聯系起來 79 *4 若不存在,則在字符串常量池中創建一個內容為"hello"的字符串對象,並將堆中的對象與之聯系起來 80 *intern 方法可以返回該字符串在常量池中的對象的引用, 81 */ 82 // string3,string4分別位於堆中不同空間 83 String string3 = new String("hello"); 84 String string4 = new String("hello"); 85 System.out.println(string3 == string4);// 輸出false 86 87 // string5,string6位於池中同一空間,常量池 88 String string5 = "hello"; 89 String string6 = "hello"; 90 System.out.println(string5 == string6);// 輸出true 91 92 // intern首先檢查字符串常量池中是否有該對象的引用,如果存在,則將這個引用返回給變量,否則將引用加入並返回給變量。 93 String string7 = new String("hello"); 94 String string8 = string7.intern(); 95 String string9 = "hello"; 96 System.out.println(string8 == string9);// true 97 98 String string10 = "hello"; 99 String string11 = new String("hello"); 100 System.out.println(string10 == string11);// 輸出false 一個在常量池中一個在堆中 101 102 /** 103 * 對於final修飾的變量,它在編譯時被解析為常量值的一個本地拷貝存儲到自己的常量池中或嵌入到它的字節碼流中。 104 * 所以此時的string12 + string13和"hello" + "World"效果是一樣的。 105 */ 106 final String STRING12 = "hello"; 107 final String STRING13 = "World"; 108 String string14 = STRING12 + STRING13; // 將兩個常量用+連接進行初始化 109 String string15 = "helloWorld"; 110 System.out.println(string14 == string15);// ture 111 112 String string16 = "hello"; 113 String string17 = "World"; 114 String string18 = string16 + string17; 115 String string19 = "helloWorld"; 116 System.out.println(string18 == string19);// false 117 System.out.println(string18.intern() == string19);// true 118 119 /** 120 * STRING20和STRING21雖然被定義為常量,但是它們都沒有馬上被賦值。在運算出string22的值之前,他們何時被賦值,以及被賦予什麽樣的值, 121 * 都是個變數。因此STRING20和STRING21在被賦值之前,性質類似於一個變量。那麽string22就不能在編譯期被確定,而只能在運行時被創建了。 122 */ 123 124 String string22 = STRING20 + STRING21; 125 String string23 = "helloWorld"; 126 System.out.println(string22 == string23);// false 127 128 /** 129 * string25 == string24當然不相等,string24雖然也是拼接出來的,但new String("lo")這部分不是已知字面量, 130 * 是一個不可預料的部分,編譯器不會優化,必須等到運行時才可以確定結果,結合字符串不變定理,所以地址肯定不同。 131 */ 132 String string24 = "Hel" + new String("lo"); 133 String string25 = "Hello"; 134 System.out.println(string25 == string24);// false 135 136 String string26 = "Hello"; 137 System.out.println(string26 == "Hello");// true 138 } 139 }
在上面我們看到Integer在-128~127之間是使用常量池的,如果不在這個區間就不會使用,其實是重新new了一個Integer,我們看一下源碼
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); }
我們看到如果i >= IntegerCache.low && i <= IntegerCache.high就會調用IntegerCache的cache方法,而不會重新new一個integer,繼續,我們找到IntegerCache這個類
1 private static class IntegerCache { 2 static final int low = -128; 3 static final int high; 4 static final Integer cache[]; 5 6 static { 7 // high value may be configured by property 8 int h = 127; 9 String integerCacheHighPropValue = 10 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); 11 if (integerCacheHighPropValue != null) { 12 int i = parseInt(integerCacheHighPropValue); 13 i = Math.max(i, 127); 14 // Maximum array size is Integer.MAX_VALUE 15 h = Math.min(i, Integer.MAX_VALUE - (-low) -1); 16 } 17 high = h; 18 19 cache = new Integer[(high - low) + 1]; 20 int j = low; 21 for(int k = 0; k < cache.length; k++) 22 cache[k] = new Integer(j++); 23 } 24 25 private IntegerCache() {} 26 }
有一個static的代碼塊,裏面初始化了一些Integer,如果範圍在-128~127之間就會從這裏面取,如果不在這個範圍內就會new一個Integer。
final類型如果不賦值是要報錯的,如果這樣賦值沒有報錯
static { asd="asd"; } public static final String asd ; { qwe="qwe"; } public final String qwe;
再看一下下面的情況
{ qwe=2; } public int qwe;
如果打應qwe的值是為2,因為斷點調試的時候public int 這行沒有執行。再看一種情況
{ qwe=2; } public int qwe=1;
如果打印qwe的值為1,因為斷點調試的時候public int這行執行了。同理如果兩個都加上static都一樣
static { qwe=2; } public static int qwe;
這個結果也是為2,因為斷點的時候public那行沒有執行,
static { qwe=2; } public static int qwe=1;
這種情況就為1了,因為是按照順序執行的。如果一個是static一個不是,又會是上面結果
{ qwe=2; } public static int qwe=1;
這種情況下結果為2,因為static先執行
{ qwe=2; } public static int qwe;
同理這種情況下也為2,盡管調試的時候public那行沒有執行,因為是static先執行的。
{ qwe=2; } public static final int qwe;
如果上面這樣寫會報錯的,提示qwe沒有初始化
static { qwe=2; } public static final int qwe;
同理上面這個結果也為2,
static { qwe=2; } public static final int qwe=1;
那麽這種就要報錯了。變量如果沒有賦初值,在調試的時候就不會執行。
Java 常量池