String str1 = "abc";
      String str2 = "abc";
      String str3 = "a" + "bc";       

      System.out.println(str1 == str2);
      System.out.println(str1 == "a" + "bc");
      System.out.println(str1 == new String("abc"));      
      System.out.println(new String("ab")+"c" == new String("ab")+"c");
输出
true
true
false
false

一般用来比较字符串是否相同的equals()是比较字符串的内容,即在堆中的字符序列。而 == 则是比较两个字符串的内存地址是否相同(堆中的引用)。

第一个true很容易理解。str1和str2都是在内存的字符串常量池中。

字符串常量池:

  • 为了减少在jvm中创建的字符串的数量,字符串类维护了一个字符串常量池,字符串常量池(String pool)是Java堆内存中一个特殊的存储区域;
  • 当创建String对象时,jvm会先检查字符串常量池,如果这个字符串的常量值已经存在在池中了,就直接返回池中对象的引用,如果不在池中,就会实例化一个字符串并放入池中;
  • 常量池:用于保存java在编译期就已经确定的,已经编译的class文件中的一份数据。包括了类、方法、接口中的常量,也包括字符串常量,如String s = "a"这种声明方式;

 

三个False:

第一个false很显然,使用了new就代表这个对象会存储在堆上。引用自然不会和在常量池中的对象一样。

 

第二个false则是因为在运行期"ab"+"c"操作实际上是编译器创建了StringBuilder对象进行了append操作后通过toString()返回了一个字符串对象存在heap上。

 

更多例子:

String s0 = "111";              //pool
String s1 = new String("111");  //heap
final String s2 = "111";        //pool 
String s3 = "sss111";           //pool
String s4 = "sss" + "111";      //pool
String s5 = "sss" + s0;         //heap 
String s6 = "sss" + s1;         //heap
String s7 = "sss" + s2;         //pool
String s8 = "sss" + s0;         //heap
 
System.out.println(s3 == s4);   //true
System.out.println(s3 == s5);   //false
System.out.println(s3 == s6);   //false
System.out.println(s3 == s7);   //true
System.out.println(s5 == s6);   //false
System.out.println(s5 == s8);   //false

对于final String s2 = "111",是一个用final修饰的变量,在编译期就已知了,在包含变量的字符串连接符"a"+s2时直接用常量"111"来代替s2,等效于"a"+"111",在编译期就已经生产了字符串对象"a111"对象在常量池中。