1.前言

String这个东西完全弄清楚其实不容易。特种兵上讲的逻辑不是很好。关于String和intern的说明建议看下美团的这篇技术文章深入解析String#intern,稍微比特种兵讲的好点,但是有些地方也没讲清楚。String,StringBuilder,StringBuffer的说明强烈看下这篇博文探秘Java中的String、StringBuilder以及StringBuffer

本文综合书本以及网络资料按照自己的理解进行了总结。希望大家多多指摘。

采用该指令查看过程
javap -c -v -l -constants Test.class >> Test.java.asm

2. String的奥秘

String在编译器处理的时候,会有不同的结果。总结规律如下:

  1. String s = "a"+"b"+1,类似这样的语句编译器在运行前直接处理成s="ab1"
  2. String对象的常量引用(包括函数返回的常量引用或者函数内部的局部变量)通过+和字符串常量字面值链接的时候,其地址值是不确定的(例1的反应了这一条)。会使用StringBuilder和append方法重新new一个对象来拼接。
  3. 重要点:String a = "hello"类似这样直接通过字面值初始化,会直接在编译时在常量池中存放具体的内容(JDK8中常量池已经放到了一个叫做metaSpace的地方,和heap是不同的内存区域)。运行时直接把常量池内的值和其heap中值对应起来。这种显式定义,若发现常量池中有其他的引用,则直接使用其他对象在常量池中的引用。
  4. 通过new来生成的对象,只会在heap中生成一个String对象;通过intern方法,可以让常量池中保存一份该heap中的引用(如果没有该值的话),地址值是一样的。但是如果使用intern方法的时候,发现已经有相同内容的其他引用了,则intern方法就失效了,仅仅获取常量池的内容,其他不受干扰。

关于intern和常量池的官方说法:
When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

PS:以下例子在JDK8中执行

例1:

/**
 * Created by Kami Wan on 2015/12/13.
 */
public class LearnString {
    public static String  getA(){return "a";}
    public static void main(String[] args) {
        String a = "a";
        final String c = "a";
        String b = a+"b";     //编译时无法确定,无法放入编译时常量池,可以采用intern方法来放入
        String d = c+"b";     //c是final类型的, c+"b"的地址可以在编译时就确定,放入编译时常量池
        String e = getA()+"b";
        String compare = "ab";

        System.out.println(b==compare);
        System.out.println(d==compare);
        System.out.println(e==compare);

    }
}

结果:
false
true
false

例2

public static void main(String[] args) {
    String s = new String("1");     //s在堆中产生一个引用对象
    s.intern();                 //在常量池保存了引用
    String s2 = "1";        //常量池已经有“1”这个对象了,所以在heap中产生对象后,直接使用s的引用
    System.out.println(s == s2);    //理论上是true(在没编译器优化这类干扰下)
 
    String s3 = new String("1") + new String("1");      //s3指向一个内容为“11”的引用对象,常量池没有保存常量
    s3.intern();        //s3在heap中的引用放入常量池,值为"11",指向S3的在heap中的引用
    String s4 = "11";       //因为发现已经有了,指向s3在堆中的引用。
    System.out.println(s3 == s4);   //所以这里也是true
}

结果:
false理论上其实应该是true可以单独执行这个黑魔法应该是编译器搞的鬼
true

例3

public static void main(String[] args) {
    String s = new String("1");     //只有heap中有对象,常量池没有"1"
    String s2 = "1";    //常量池出现了“1”,但是和s没关系,和s2在heap中地址有关系
    s.intern();         //放入常量池,发现已经有了,仅仅把内容返回,地址关联不变化
    System.out.println(s == s2);             
 
    String s3 = new String("1") + new String("1");      //只有heap中有对象,常量池没有"11"
    String s4 = "11";       //常量池有“11”并且还是s4引用关联
    s3.intern();      
    System.out.println(s3 == s4);
}


结果:
false
false

3.String a ="aaa"和String a = new String("aaa")

String 创建对象的两种方式:
String s = new String(“aaa”);
分析:(1)首先在String Pool 中查找 是否有aaa 这个字符串对象,如果有,则不在String Pool中创建”aaa”这个对象了,直接在heap(堆)中创建”aaa” 这个字符串对象,然后将堆中这个aaa 地址返回给赋值给 s这个引用,所有s 指向了堆中创建的这个”aaa”这个字符串对象
(2)如果String pool中没有aaa这个对象,那么首先在String中创建一个aaa对象,然后在堆中创建aaa对象,并将堆中这个对象的引用地址返回给 s 这个引用

String s = “aaa”;
(1)首先检查String pool 中是否有aaa这个对象,如果没有,则在String pool中创建一个aaa 这个对象,然后将Sting pool中的这个地址返回,S 就指向了String pool
(2)如果在String pool 有aaa 这个对象,那么直接将aaa 的地址返回给S