常量池
Java中的常量池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复重复创建相等变量时节省了很多时间。常量池其实也就是一个内存空间,不同于使用new关键字创建的对象所在的堆空间。
常量池项共分为11种类型:
常量池项类型 | 值 | 说明 |
CONSTANT_Utf8 | 1 | UTF-8编码的Unicode字符串 |
CONSTANT_Integer | 3 | int型常量 |
CONSTANT_Float | 4 | Float型常量 |
CONSTANT_Long | 5 | Long型常量 |
CONSTANT_Double | 6 | double型常量 |
CONSTANT_Class | 7 | 对一个class的符号引用 |
CONSTANT_String | 8 | String型常量 |
CONSTANT_Fieldref | 9 | 对一个字段的符号引用 |
CONSTANT_Methodref | 10 | 对一个类方法的符号引用 |
CONSTANT_InterfaceMedthodref | 11 | 对一个接口方法的符号引用 |
CONSTANT_NameAndType | 12 | 对名称和类型的符号引用 |
例子1:常量池中对象和堆中的对象
1 Integer i1 = new Integer(1); 2 Integer i2 = new Integer(1); 3 // i1,i2分别位于堆中不同的内存空间 4 System.out.println(i1 == i2);// 输出false 5 6 Integer i3 = 1; 7 Integer i4 = 1; 8 // i3,i4指向常量池中同一个内存空间 9 System.out.println(i3 == i4);// 输出true 10 11 // 很显然,i1,i3位于不同的内存空间 12 System.out.println(i1 == i3);// 输出false
例子2:
Java中基本类型的包装类的大部分都实现了常量池技术,这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外Byte,Short,Integer,Long,Character 这5种整型的包装类也只是在对应值小于等于127时才可使用对象池,也即对象不负责创建和管理大于127的这些类的对象。
1 // 5种整形的包装类Byte,Short,Integer,Long,Character的对象, 2 // 在值小于127时可以使用常量池 3 Integer i1 = 127; 4 Integer i2 = 127; 5 System.out.println(i1 == i2);// 输出true 6 7 // 值大于127时,不会从常量池中取对象 8 Integer i3 = 128; 9 Integer i4 = 128; 10 System.out.println(i3 == i4);// 输出false 11 12 // Boolean类也实现了常量池技术 13 Boolean bool1 = true; 14 Boolean bool2 = true; 15 System.out.println(bool1 == bool2);// 输出true 16 17 // 浮点类型的包装类没有实现常量池技术 18 Double d1 = 1.0; 19 Double d2 = 1.0; 20 System.out.println(d1 == d2);// 输出false
例子3:同样的,String也实现了常量池技术
1 // s1,s2分别位于堆中不同空间 2 String str1 = new String("hello"); 3 String str2 = new String("hello"); 4 System.out.println(str1 == str2);// 输出false 5 6 // s3,s4位于池中同一空间 7 String str3 = "hello"; 8 String str4 = "hello"; 9 System.out.println(str3 == str4);// 输出true
例子4:关于Integer类的valueOf方法的一个小问题
1 //代码段A 2 Integer a1 = 127; 3 Integer b1 = 127; 4 System.out.println(a1 == b1);//输出true 5 //代码段B 6 Integer a2 = 128; 7 Integer b2 = 128; 8 System.out.println(a2 == b2);//输出false
原因:当你直接给一个Integer对象一个int值的时候,实际上却自动调用了valueOf方法,然而valueOf方法的内部实现是这样的:
1 public static Integer valueOf(int i) { 2 final int offset = 128; 3 if (i >= -128 && i <= 127) { 4 return IntegerCache.cache[i + offset]; 5 } 6 return new Integer(i); 7 }
因为代码段B里赋予的值128溢出了,没有进入if (i >= -128 && i <= 127)条件语句,返回IntegerCache 类的静态数组的数据,IntegerCache类的内部实现是这样的:
1 private static class IntegerCache { 2 private IntegerCache() { 3 } 4 static final Integer cache[] = new Integer[-(-128) + 127 + 1]; 5 6 static { 7 for (int i = 0; i < cache.length; i++) 8 cache[i] = new Integer(i - 128); 9 }
而是直接执行了return new Integer(i)语句,又重新new了一个Integer对象出来,即代码段B实际上就相当于:
1 Integer a2 = new Integer(128); 2 Integer b2 = new Integer(128);
补充说明:关于上文提到的IntegerCache类,由于cache[]在IntegerCache类中是静态数组,也就是只需要初始化一次,即static{……}部分,所以,如果Integer对象初始化时是-128~127的范围,就不需要再重新定义申请空间,都是同一个对象—在IntegerCache.cache中,这样可以在一定程度上提高效率。
例子5:关于String类的intern方法
当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。
它遵循以下规则:对于任意两个字符串s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern()才为true。
例如:
1 String str1 = new String("abc"); 2 String str2 = str1.intern(); 3 if (str1 == str2) { 4 System.out.println("字符串abc在常量池中"); 5 } else { 6 System.out.println("字符串abc不在常量池中"); 7 } 8 9 String str3 = "abc"; 10 String str4 = str3.intern(); 11 if (str3 == str4) { 12 System.out.println("字符串abc在常量池中"); 13 } else { 14 System.out.println("字符串abc不在常量池中"); 15 }
还有,
1 String str1 = "abc"; 2 String str2 = new String("abc"); 3 String str3 = new String("abc"); 4 str2.intern();// 不起作用的代码 5 str3 = str3.intern();// 起作用 6 System.out.println(str1 == str2);// 输出:false 7 System.out.println(str1 == str2.intern());// 输出:true 8 System.out.println(str1 == str3);// 输出:true