Java 常量池
最近在网上看到一些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 int mInteger3 = 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;
那么这种就要报错了。变量如果没有赋初值,在调试的时候就不会执行。