首页 / JAVA  

java中字符串常量池详解

字符串常量池会用来存放字符串,也就是说常量池中的文本字符串会在类加载时进入字符串常量池。

那字符串常量池和运行时常量池是什么关系呢?上面我们说常量池中的字面量会在类加载后进入运行时常量池,其中字面量中有包括文本字符串,显然从这段文字我们可以知道字符串常量池存在于运行时常量池中。

字符串对象创建的过程

String t = new String("str");

由于”str”属于字面量,那么它会在类加载之后存在于字符串常量池中,也就是说,在 String t = new String(“str”)这句代码执行之前,字符串常量池就已经创建了”str”这个字符串对象了,我们都知道,new这个关键字会在堆中创建一个对象。

所以,这段代码创建了两个对象。一个在堆中,一个在字符串常量池中。

String t1 = new String("str");
String t2 = new String("str");

这个过程程序创建了三个对象,

首先String t1 = new String("str");执行时会在字符串常量池中创建“str”对象,然后news时会在堆中创建一个“str”对象,并将堆中的引用地址赋值给t1;

由于字符串常量池中只会保存一份同样的字符串,所以

String t2 = new String("str");

在执行时,字符串常量池中不会创建“str”对象,new时会在堆中创建一个“str”对象,并将堆中的引用地址赋值给t2;


String t = "str";

上面代码创建一个对象,在这段代码执行之前,字符串常量池已经创建了一个”str”的对象,但由于这行代码并非用new的方法,所以虚拟机会在字符串常量池中寻找是否有内容为”str”的字符串对象,如果有,则直接返回这个字符串的引用,所以最终结果只创建了一个对象。

String 的intern方法

例如我们调用了t.intern()。

在JDK1.6的时候,调用了这个方法之后,虚拟机会在字符串常量池在查找是否有内容与”str”相等的对象,如果有,则返回这个对象,如果没有,则会在字符串常量池中添加这个对象。注意,是把这个对象添加到字符串常量池。

到了JDK1.7之后,如果调用了intern这个方法,虚拟机会在字符串常量池在查找是否有内容与”str”相等的对象,如果有,则返回这个对象,如果没有。则会在堆中创建这个对象并把引用复制添加到字符串常量池中。注意,这个时候创建的对象是在堆中的引用。

(注:调用t.intern()方法,如果常量池存在该字符串对象,并不会做任何事,也就是不会对t产生任何影响)

现在开始来分析问题中的代码

t1 = new String(“1”)。

这句代码执行之前,字符串常量池中已经有”t”这个对象,执行之后会在堆中也创建一个”t”的对象,此时t1指向的是堆中的对象。

t1.intern();

这句代码执行之后,会在字符串常量池寻早内容为”t”的对象,字符串常量池已经存在这个对象了,把这个对象返回(不过返回之后并没有变量来接收)。

t2 = “1”。

这句执行后会在字符串常量池查找内容为”t”的对象,字符串常量池已经有这个对象了,返回给t2,此时t2指向的是常量池中的对象。

一个是常量池中的对象,一个是在堆中的对象,两者能相等吗?因此

t1 与 t2不相等。

t3 = new String(“2”) + new String(“2”);

这段代码调用之前,字符串常量池有一个”2”的对象,执行之后,实际上会调用StringBuilder的append()方法类进行拼接,最后在堆中创建一个”22”的对象,注意,此时字面量并没有”22”这个字符串,也就是说在字符串常量池并没有”22”这个对象。此时t3指向堆中”22”这个对象

t3.intern();

执行这个方法之后

在JDK1.6的时候,它在字符串常量池中并没有找到内容为”22”的对象,所以这个时候会在字符串常量池中创建“22”这个对象,并把这个对象返回(此时并没有变量来接收这个返回的对象)。注意是在常量池中创建了对象,而不是把堆中的“22”对象的引用添加到常量池中。

t4 = “22”。

这句代码执行后,会返回字符串常量池中内容为”22”对象,此时t4指向的是字符串常量池中的对象。

显然,一个对象在字符串常量池,一个在堆中,两个对象并非是同一个对象,因此在JDK1.6的时候,t3与t4不相等。

但是在JDK1.7的时候

t3.intern()执行之后,由于在字符串常量池在并没有内容为”22”的对象,所以会把堆中的“22”对象的引用赋值到字符串常量池。注意此时字符串常量池保存的是堆中这个对象的引用。

t4 = “22”。

执行这句代码之后,从字符串常量池返回给t4的是堆中对象的引用。此时t4指向的实际上是堆中对象的引用,也就是说,t3和t4指向的是同一个对象。

因此t3与t4相等。

除了字符串常量池,在java中还有基本类型的封装类常量池,包括:

Byte, Short, Integer, Long, Character, Boolean。需要注意的是,Float和Double这两个类并没有对应的常量池。

注:
1.上面5种整型的包装类也只是在对象数值在-128~127才可以使用这些常量池。
2.在周志明的那本虚拟机中有这样一句话:包装类的
“\==”运行符在不遇到算术运算的情况下不会自动拆箱,以及他们的equals()方法不处理数据类型的关系,可以推断出如果遇到“==”两边有算术运算是话就会自动拆箱和进行数据类型转换处理。

3.Long的equals方法会先判断是否是Long类型。

4.无论是Integer还是Long,他们的equals方法比较的是数值。

(注:基本数据类型比较的是数值,封装类型比较的是对象)

代码:
    Integer a = 1;
    Integer b = 2;
    Integer c = 3;
    Integer d = 3;
    Integer e = 321;
    Integer f = 321;
    Long g = 3L;
    System.out.println(c == d);  // true
    System.out.Println(e == f);  // false 
    System.out.println(c == (a + b)); 
    System.out.println(c.equals(a+b));
    System.out.println(g == (a + b));
    System.out.println(g.equals(a + b));
System.out.println(c == d)。

由于常量池的作用,c与d指向的是同一个对象(注意此时的==比较的是对象,也就是地址,而不是数值)。因此为true

System.out.println(e == f)。

由于321超过了127,因此常量池失去了作用,所以e和f数值虽然相同,但不是同一个对象,以此为false。

System.out.println(c == (a+b))。

此时==两边有算术运算,会进行拆箱,因此此时比较的是数值,而并非对象。因此为true。

System.out.println(c.equals(a+b))

c与a+b的数值相等,为true。

System.out.pirnln(g == (a + b))

由于==两边有算术运算,所以比较的是数值,因此为true。

System.out.println(g.equals(a+b))。

Long类型的equal在比较是时候,会先判断a+b是否为Long类型,显然a+b不是,因此false
2019-10-18