1. 使用双引号创建字符串时,JVM会现在字符串常量池中查找是否已存在该字符串,存在则返回,不存在则在池中创建后再返回。与此同时,使用String的intern方法也是类似处理。

  2. 使用new String的方式创建,或者使用+拼接变量时,JVM都会重新创建一个新对象。比如下面:

    String s1 = "1";
    String s2 = "12";
    String s3 = s1 + "2";
    System.out.println(s2 == s3); // false
    

    因为s1是引用变量,JVM在编译期不能确定s1的值,所以会在堆中新创建一个对象指向s3。如果s1加个final限制,让s1能在编译阶段确定下来,它就是常量,因此在编译时,s1 + "2"就会被优化成12。

    final String s1 = "1";
    String s2 = "12";
    String s3 = s1 + "2";
    System.out.println(s2 == s3); // true
    

    至于等号比较为什么相等,是因为s2已经在常量池中了,s3被优化为了12,自然不会新创建了, 它跟s2指向的是同一个对象。

  3. new String("123")创建了几个对象?一个或两个。先从常量池中查找123,有则创建,没有则不创建;new会新创建一个对象。

String为什么不可变

不能被继承;未提供能改变它状态的公共方法;它的工具方法都是返回一个新的字符串。

String不可变有什么好处

  • String常量池的必要条件。String常量池就是一个由JVM创建的特殊内存区域,用于存放字符串常量,当创建一个字符串时,JVM先去池中寻找是否存在,存在则返回,不存在则创建。即多个具有相同字符序列的字符串引用会指向池中同一个对象。如果有一个引用变量对其修改,那其他引用变量的值随之修改,缓存就无意义了。说到这里,我想到了平时写缓存代码的逻辑,我们使用Redis来缓存重要对象数据的时候,当缓存对象发生修改,我们会立即更新缓存,那字符串常量池也能这么做吗,能在常量池中实时更新它的值或内部状态吗?显然是不能的。如果能修改,首先不能称它为常量池了;其次这会大大提升它的使用难度,很多判断对象相等的场景变得复杂了;其三,字符串本质是字符序列,它不应该有复杂的内部状态,越简单越安全,否则它也不会被广泛使用。

  • 因为不可变,所以是线程安全的。

  • 很适合作为HashMap的Key。String的hashCode方法在第一次调用的时候会缓存它的hash值,再次调用的时候无需再次计算。关于这一点,我们去看它的源码就会一目了然:将hash函数的计算结果赋值给String的成员变量hash,第二次计算就能直接返回。从这里可以看出,从HashMapHashSet等使用hash函数计算元素位置的集合中查找一个元素是非常快的。