Java中字符串的创建过程及intern()方法

一、字符串的创建过程

1. String s = "abc"

  • 首先在字符串常量池中查找是否有"abc"
  • 如果常量池中没有"abc",则创建一个"abc"对象放入常量池,进行下一步;如果有,直接进行下一步
  • 变量 s 指向常量池中的"abc"对象

2. String s = new String("abc");

创建过程:

  • 首先在字符串常量池中查找是否有"abc"
  • 如果常量池中没有"abc",则创建一个"abc"对象放入常量池,进行下一步;如果有,直接进行下一步
  • 在堆中创建一个String对象,用常量池中的"abc"初始化堆中的String对象
  • 变量 s 指向堆中的String对象,而不是常量池中的"abc"对象

对比

String s = "abc";  String s = new String("abc");
创建对象数量 1(如果常量池已有,则为0) 2(如果常量池已有,则为1)
存储位置 仅常量池 常量池+堆

二、intern()方法

String类中的本地方法,作用为:
当字符串调用intern()方法时,如果字符串常量池中已经包含了一个与该字符串对象相等的字符串(通过equals()方法判断),则返回字符串常量池中的那个字符串,否则将该字符串对象添加到池中,并返回该对象的引用。

代码示例1

public class StringTest {
    public static void main(String[] args) {
        String s1 = new String("abc");
        String s2 = s1.intern();
        String s3 = "abc";
        System.out.print(s1 == s3);
        System.out.print(" ");
        System.out.print(s2 == s3);
    }
}

执行流程:

  • String s1 = new String("abc")会根据前述new String("abc")的创建过程来创建对象,s1指向堆中的String对象
  • s1.intern()判断字符串常量池中是否有字符串在内容上和s1相同(即通过equals()比较),由于上一步的创建过程中在字符串常量池中创建"abc"对象,因此判断为有直接返回字符串常量池中的"abc"给s2
  • String s3 = "abc"在字符串常量池中查找"abc",由于存在,该过程同样不需要在字符串常量池中创建对象,s3指向字符串常量池中的"abc"
  • 由于s1指向堆中String对象,s2和s3指向字符串常量池中的对象"abc",因此输出为:false true

代码示例2

public class StringTest {
    public static void main(String[] args) {
        String s1 = new String("abc") + new String("def");
        String s2 = s1.intern();
        String s3 = "abcdef";
        System.out.print(s1 == s3);
        System.out.print(" ");
        System.out.print(s2 == s3);
    }
}

执行流程:

  • String s1 = new String("abc") + new String("def")涉及字符串的拼接(具体见下一部分)。①此处会先在堆中创建一个StringBuilder对象;②再根据前述new String("abc")的创建过程来创建String对象(内容为"abc"),然后调用StringBuilder的append()方法将"abc"添加到StringBuilder对象的末尾,同理将"def"添加到StringBuilder对象的末尾;③最后调用StringBuilder的toString()方法转化为字符串,需要注意的是此处不会在字符串常量池中创建"abcdef"对象。s1指向堆中的String对象(由String Builder的toString方法创建)
  • s1.intern()判断字符串常量池中是否有字符串在内容上和s1相同(即通过equals()比较),由于上一步的创建过程中不会在字符串常量池中创建"abc"对象,因此判断为,此时会根据JDK版本不同而执行不同操作:JDK7之前(不含7),复制String对象s1到字符串常量池,并返回字符串常量池中的对象;JDK7开始,将String对象s1的引用存放在字符串常量池中,并返回该引用
  • String s3 = "abc"在字符串常量池中查找"abc",由于存在,该过程不需要在字符串常量池中创建对象,s3指向字符串常量池中的"abc"
  • s1指向堆中String对象。JDK7之前,s2和s3指向字符串常量池中的对象"abc",因此输出为:false true;JDK7开始,s2和s3指向字符串常量池中的对象引用,即指向String对象s1,因此输出为:true true

三、字符串拼接

通过+进行字符串拼接,分为无变量参与的和有变量参与的 

1. 无变量参与

仅使用常量进行拼接时,拼接结果存放在常量池,因为编译期会进行优化,直接将拼接结果作为字符串字面量。

代码示例3
public class StringTest {
    public static void main(String[] args) {
        String s1 = "abc" + "def";
        String s2 = "abcdef";
        System.out.println(s1 == s2);
    }
}

输出为true,因为字符串常量池中不会存在相同内容的常量,s1和s2指向的字符串常量池中的对象是同一个。

代码示例4
public class StringTest {
    public static void main(String[] args) {
        final String s = "abc";
        String s1 = s + "def";
        String s2 = "abcdef";
        System.out.println(s1 == s2);
    }
}

输出为true,因为s被final修饰,在编译期就能确定不会改变,s + "def"进行拼接时也能在编译器进行优化,直接存放在字符串常量池。

2. 有变量参与

只要其中有一个是变量,拼接的结果就在堆中。因为此时是通过StringBuilder进行拼接的,先调用append()方法添加字符串,再调用toString()方法转化为String对象并返回。

注:值得注意的是,StringBuilder的toString()方法中虽然也是new String(),但这里的参数是字符数组,与前面new String("abc")使用String对象作为参数有所不同,使用字符数组作为参数时不需要使用字符串常量池中的内容相同的对象,自然也不会在字符串常量池中创建对象。【解释了代码示例2与代码示例1的不同】

代码示例5

public class StringTest {
    public static void main(String[] args) {
        String s = "abc";
        String s1 = s + "def";
        String s2 = "abcdef";
        System.out.println(s1 == s2);
    }
}

输出为false,s1为堆中对象,s2为字符串常量池中对象。

你可能感兴趣的:(java,开发语言,String,intern,StringBuilder)