一文搞定Java常量池技术重要考点

常量池技术

概念

Java 是一种动态连接语言,在Java程序中,有很多东西不会在运行过程中变化。而这些在JVM解释执行程序的时候是非常重要的。那么编译器将源程序编译成class文件后,会用一部分字节分类存储这些代码。而这些字节我们就称为常量池。

基本类型的包装类和常量池

java中基本类型的包装类的大部分都实现了常量池技术,这些类是Byte,Short,Integer,Long, Character,Boolean,另外两种浮点数类型的包装类则没有实现。

Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用对象池,也即对象不负责创建和管理大于127的这些类的对象

//5种整形的包装类Byte,Short,Integer,Long,Character的对象,
//在值小于127时可以使用常量池
 
Integer i1=127;
Integer i2=127;
System.out.println(i1==i2)//输出true
 
//值大于127时,不会从常量池中取对象

Integer i3=128;
Integer i4=128;
System.out.println(i3==i4)//输出false
 
//Boolean类也实现了常量池技术
 
Boolean bool1=true;
Boolean bool2=true;
System.out.println(bool1==bool2);//输出true
 
//浮点类型的包装类没有实现常量池技术
 
Double d1=1.0;
Double d2=1.0;
System.out.println(d1==d2)//输出false

Integer和int相关考点

  1. 两个new Integer(),即使值相等,生成的是两个对象,地址不同,因此==判断为不相等

  2. 两个Integer变量如果值在-128至127之间,则地址都在常量池中,==判断为相等;如果超过这个范围,则不相等。

    int数值直接赋给Integer变量时,Integer变量会调用Integer.valueOf()方法包装起来,而valueOf方法仍然会优先考虑使用常量池

  3. Integer变量和new Integer(),即使值相等,地址不同,==判断为不相等

  4. int与Integer、new Integer()比较,后两者会自动拆箱为int类型,因此只要值相等,==判断为相等

  5. 如果Integer变量之间存在运算,则自动拆箱,之后再计算出结果,最后包装,使用Integer.valueOf()方法。

int i = 128;
Integer i2 = 128;
Integer i3 = new Integer(128);

System.out.println("i == i2 = " + (i == i2)); // Integer会自动拆箱为int,所以为true
System.out.println("i == i3 = " + (i == i3)); // true,理由同上

Integer i4 = 127;// 编译时被翻译成:Integer i4 = Integer.valueOf(127);
Integer i5 = 127;
System.out.println("i4 == i5 = " + (i4 == i5));// true

Integer i6 = 128;
Integer i7 = 128;
System.out.println("i6 == i7 = " + (i6 == i7));// false

Integer i8 = new Integer(127);
System.out.println("i5 == i8 = " + (i5 == i8)); // false

Integer i9 = new Integer(128);
Integer i10 = new Integer(128);
System.out.println("i9 == i10 = " + (i9 == i10)); // false
Integer a = new Integer(127), b = new Integer(128);
int c = 127, d = 128, dd = 128;
Integer e = 127, ee = 127, f = 128, ff = 128;

System.out.println(a == b); // false 因为a,b都是new出来的对象,地址不同所以为false
System.out.println(a == c); // true a会自动拆箱为int类型
System.out.println(a == e); // false 指向的地址不同a是new出来的

System.out.println(e == c); // true e会自动拆箱为int类型
System.out.println(e == ee);// true Integer对 处于-128到127范围之间,指向了同一片地址区域

System.out.println(b == f); // false 指向的地址不同b是new出来的
System.out.println(f == d); // true f自动拆箱为int类型

System.out.println(f == ff); /*
							 * false 指向的不是同一片地址区域。
							 * 在Integer类型中,-128到127存放的是同一片区域地址,
							 * 之外的数是另外开辟空间来进行 存储的
							 */
System.out.println(d == dd); // true 不解释

Integer i01 = 59;
int i02 = 59;
Integer i03 =Integer.valueOf(59);
Integer i04 = new Integer(59);

以下输出结果为false的是:

System.out.println(i01== i02);//true
System.out.println(i01== i03);//true
System.out.println(i03== i04);//false
System.out.println(i02== i04);//true
Integer i01 = 128;
int i02 = 128;
Integer i03 = Integer.valueOf(128);
Integer i04 = new Integer(128);

以下输出结果为false的是:
System.out.println(i01 == i02);//true
System.out.println(i01 == i03);//false
System.out.println(i03 == i04);//false
System.out.println(i02 == i04);//true

String相关考点

初始化字符串new String()、“ ”方式的区别及拘留字符串概念

源代码中所有相同字面值的字符串常量只可能建立唯一 一个拘留字符串对象。可以调用String的intern()方法来使得一个常规字符串对象成为拘留字符串对象

  1. String s=new String(“Hello world”); 编译成class文件后的指令:事实上,在运行这段指令之前,JVM就已经为"Hello world"在堆中创建了一个拘留字符串( 值得注意的是:如果源程序中还有一个"Hello world"字符串常量,那么他们都对应了同一个堆中的拘留字符串)。然后用这个拘留字符串的值来初始化堆中用new指令创建出来的新的String对象,局部变量s实际上存储的是new出来的堆对象地址

  2. String s=“Hello world”:这跟(1)中创建指令有很大的不同,此时局部变量s存储的是早已创建好的拘留字符串的堆地址

//s1,s2分别位于堆中不同空间
String s1=new String("hello");
String s2=new String("hello");
System.out.println(s1==s2);//输出false
//s3,s4位于池中同一空间
String s3="hello"; 
String s4="hello";
System.out.println(s3==s4);//输出true

用new String()创建的字符串不是常量,不能在编译期就确定,所以new String()创建的字符串不放入常量池中,他们有自己的地址空间。

String 对象(内存)的不变性机制会使修改String字符串时,产生大量的对象,因为每次改变字符串,都会生成一个新的String。 java 为了更有效的使用内存,常量池在编译期遇见String 字符串时,它会检查该池内是否已经存在相同的String 字符串,如果找到,就把新变量的引用指向现有的字符串对象,不创建任何新的String 常量对象,没找到再创建新的。所以对一个字符串对象的任何修改,都会产生一个新的字符串对象,原来的依然存在,等待垃圾回收。

字符串拼接

采用“+”运算符拼接字符串时:

  1. 如果拼接的都是字符串直接量(如“Hello”),编译器直接优化为一个完整的字符串,编译期间字符串常量值已经确定,此时如果常量池已经存在该字符串,则直接引用
String a = "a1"; 
String b = "a" + 1; 
System.out.println((a == b)); //result = true
String a = "atrue"; 
String b = "a" + "true"; 
System.out.println((a == b)); //result = true
String a = "a3.4"; 
String b = "a" + 3.4; 
System.out.println((a == b)); //result = true
  1. 如果拼接的字符串中包含变量,则编译时采用StringBuilder对其进行优化。自动创建StringBuilder实例并调用append方法。编译期间无法确定字符串常量,只有运行期间动态分配并将新地址赋予String变量。
String a = "ab"; 
String bb = "b"; 
String b = "a" + bb; 
System.out.println((a == b)); //result = false
  1. 但使用final关键字修饰变量后,编译期间会被解析为常量值的一个本地拷贝存储到常量池或者嵌入到字节码流中。
String a = "ab"; 
final String bb = "b"; 
String b = "a" + bb; 
System.out.println((a == b)); //result = true
  1. 而对于在方法中返回常量,通过调用该方法来给String变量赋值的情况下,编译期间无法确定,只有在程序运行期间调用方法后才能将返回值和字符串“a”动态连接并分配地址。
String a = "ab"; 
final String bb = getBB(); 
String b = "a" + bb; 
System.out.println((a == b)); //result = false 
private static String getBB() {
	return "b"; 
}

intern()方法

String的intern()方法会查找在常量池中是否存在一份equal方法相等的字符串,如果有则返回一个引用,没有则添加自己的字符串进入常量池,注意:只是字符串部分。所以这时会存在2份拷贝,常量池的部分被String类私有并管理,现存对象拷贝则按对象生命周期继续使用。

对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。

class Test
{
	private static String a = "ab"; 
	public static void main(String[] args)
	{
	String s1 = "a";
	String s2 = "b";
	String s = s1 + s2;
	System.out.println(s == a);//false
	System.out.println(s.intern() == a);//true  
	}
}

参考

  1. https://www.cnblogs.com/step-by-step1/p/3469498.html
  2. https://blog.csdn.net/xiaojin21cen/article/details/79832173
  3. https://blog.csdn.net/zhangyaqingjjy/article/details/8685498

你可能感兴趣的:(Java,java,字符串,jvm)