深入解析Java中的String

1. String类

String是开发中使用最频繁的类。它是一个不可变的类,也即它使用了final修饰了类的声明,同时,对String的修改操作并没有改变原有的String,而是返回了一个新的String对象,比如String的连接操作、String::subString、String::replace…

因为String使用的频繁程度大,所以jdk团队,将其使用简化,让其定义方式跟8大基本数据类型一样简单:

String str="jdk";

上面这种方式,也被称为 以字面量的形式定义字符串对象

再居于String的不可变设计,设计了字符串常量池(String Pool) 来进行String对象的复用。也即以字面量形式定义字符串对象、或者调用String::intern() 后,会将字符串对象的引用放入字符串常量池。

上面特意强调了 String str=“jdk”; 这种方式是为了得到一个String对象,也就是说str指向的是一个String对象,对象的内容是 字面量"jdk"。那么这个对象是怎样产生的呢?
答案是 当以字面量的形式定义字符串对象时,会先看String pool里面是否存在一个内容与该字面量相同的String对象引用(.equal(“jdk”)),存在直接返回该引用,不存在的话,在堆中创建一个内容为"jdk"的String对象,并把该对象的用用放入String pool,同时赋给str。(jdk1.6描述不同,本文默认以1.7之后版本)

补充:
String pool放在方法区中。在jdk1.6中,以永久代形式实现方法区,故在永久代,jdk1.7及之后移到了堆中。

2. String的拼接操作

String 的拼接操作使用也很常见,但这里面涉及很多隐藏知识。
以几个例子说明:

1) 字面量的拼接:

@Test
    public void test1(){
    
    
        String s1="a"+"b";
        String s2="ab";
        System.out.println(s1==s2);//true
    }

上面的s1,在经过javac编译后,会直接优化为:(反编译后的代码)

  @Test
    public void test1() {
    
    
        String s1 = "ab";
        String s2 = "ab";
        System.out.println(s1 == s2);
    }

2) 含字符串变量的拼接:

	@Test
    public void test3(){
    
    
        String s1="a";
        String s2="b";
        String s3="ab";
        String s4=s1+s2;
        String s5=s1+"b";
        System.out.println(s3==s4);// false
        System.out.println(s3==s5);// false
    }

当进行拼接的操作中涉及到 变量时,这里在编译期间会进行一种优化处理:即创建一个StringBuilder对象,然后将进行拼接的字符串以StringBuilder::appen()的方式拼接,最后调用StringBuilder::toString方法返回最后的String对象。
使用Idea的 jclasslib插件可以看到编译后的class文件信息:
在这里插入图片描述

现在就可以理解上面程序执行的结果了,s4、s5指向的是各自新生成的对象,与s3这个之前就存在的对象的引用自然不同。

3) 常量字符串的拼接:

@Test
    public void test2(){
    
    
        final String s1="a";
        final String s2="b";
        String s3="ab";
        String s4=s1+s2;
        System.out.println(s3==s4);//true
    }

使用了final修饰,此时的s1、s2就是个常量,javac在编译时,也会直接将s4优化为s4=“ab”。

3.究竟产生了几个对象问题:

说明:默认示例中的字面量在之前是没有是没出现过的。

示例1:

String str="jdk"+"niu";

这里经过javac编译后,只会存在一个"jdkjava"一个字面量,则再jvm加载字节码时,只会产生内容为"jdkniu"这一个对象。

示例2:

String s1=new String("jdk");

这里出现了"jdk"字面量,也就是说会产生一个内容为"jdk"的对象,并把其引用放入String pool。然后new操作也会产生一个对象,内容也为"jdk"(这里两个对象底层实际存放字符的char []引用的是同一个)。所以这里一共产生了2个对象。

示例3:

String str=new String("1")+new String ("2");

根据示例2,可以得到已经存在4个String对象。
前面讲过,对于这种含变量的拼接操作,会创建StringBuilder,进行append,最后使用toString返回一个内容为"12"的String对象。也就是说还会产生一个StringBuilder对象和一个String对象。
这里既然StringBuilder的toString方法要返回一个内容为"12"的String对象,那么"12"也要产生一个String对象?答案是错的。看看StringBuilder源码:

public String toString() {
    
    
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

这里是将存储字符的数组value作为参数传入构造器,与new String(字面量)这种形式不同,并没有涉及到 字面量,所以不会产生一个String对象。

4.intern:

前面提到过,除了使用 字面量形式定义字符串对象以外,还能使用String::intern()方法,来动态扩充String pool。

示例1:

	@Test
    public void test6(){
    
    
        String s1=new String ("1");
        String s2 = s1.intern();
        String s3="1";
        System.out.println(s1==s2);//false
        System.out.println(s1==s3);//false
        System.out.println(s2==s3);//true
    }

"1"产生一个对象:ref1
new String(“1”)本身产生一个对象:ref2
s1== ref2;
s2== ref1,s3==ref1

这里主要是s1.intern()执行前,String pool已经存在了 值为"1"的字符串引用,故是s1执行了intern,但s1!=s2。
示例2:

		String s1=new String("2")+new String("2");
        s1.intern();
        String s3="22";

        System.out.println(s1==s3);//true

这里结果为true的原因类比上面,是因为s1.intern();执行前 String pool中并没有值为"12"的字符串引用,这时执行s1.intern(),会把s1的引用放入String pool。

猜你喜欢

转载自blog.csdn.net/qq_40728028/article/details/99717035