声明为final,不可被继承
实现Serializable接口,表示其支持序列化
实现Comparable接口,表示其可比较大小
JDK8内部使用final char[] value存储字符串数据;JDK9改为byte[](节约空间,因为大部分String对象都是Latin-1字符,此类字符只需要一个byte存储)
不可变的字符序列,下面三种情况都需要重新指定内存区域赋值,不能使用原有的value进行赋值:
String s1 = "psj";
String s2 = "psj";
s2 ="psw";
System.out.println(s1==s2); // false
String s1 = "psj";
String s2 = "psj";
s2 += "2"
System.out.println(s1); // psj
System.out.println(s2); // psj2
通过字面量给字符串赋值(即String a = "psj")时,字符串值声明在堆中的字符串常量池中
String s1 = "psj";
String s2 = s1.replace('j', 'w')
System.out.println(s1); // psj
System.out.println(s2); // psw
// 源代码
public void test1(){
String s1 = "a" + "b";
String s2 = "ab";
System.out.println(s1 == s2); // true
System.out.println(s1.equals(s2)); // true
}
// 编译后的字节码文件
public void test1() {
String s1 = "ab";
String s2 = "ab";
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
public void test2() {
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop"; // 编译期优化
String s5 = s1 + "hadoop"; // 拼接中出现变量,则相当于在堆中new String()
String s6 = "javaEE" + s2; // 拼接中出现变量,则相当于在堆中new String()
String s7 = s1 + s2; // 拼接中出现变量,则相当于在堆中new String()
System.out.println(s3==s4); // true
System.out.println(s3==s5); // false
System.out.println(s3==s6); // false
System.out.println(s3==s7); // false
System.out.println(s5==s6); // false
System.out.println(s5==s7); // false
System.out.println(s6==s7); // false
// intern():判断字符串常量池中是否存在"javaEEhadoop"
// 存在则返回常量池中该字符串的地址
// 不存在则在常量池中加载一份该字符串并返回该对象地址
String s8 = s6.intern();
System.out.println(s3==s8); // true
}
变量拼接原理是StringBuilder(JDK8):
// 源代码:
public void test3() {
String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
System.out.println(s3 == s4); // false
}
// 字节码文件:对于String s4 = s1 + s2
StringBuilder s4 = new StringBuilder();
s4.append("a");
s4.append("b");
s4.toString(); // 相当于new String("ab"),这个对象在非字符串常量池部分的堆空间中
注意:只有变量的拼接才是使用StringBuilder,常量或常量引用的拼接依旧使用编译期优化:
public void test4() {
final String s1 = "a";
final String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
System.out.println(s3 == s4); // true(s1和s2相当于常量)
}
循环进行拼接操作时,直接创建一个StringBuilder对象进行append操作比先创建一个String s = ""再使用加号进行拼接要快许多:
后者需要创建多个StringBuilder进行append操作,并且还要进行new String(...)操作
后者创建对象较多,占用空间大,GC时间长
// 以下两种方式保证变量s指向的是字符串常量池中的数据
String s = "psj";
String s = new String("psj").intern();

String s = new String("1"); // s指向堆空间
s.intern(); // 调用该方法前,字符串常量池中已经存在"1"
String s2 = "1";
System.out.println(s == s2); // false,因为一个是堆空间中的对象,一个是字符串常量池中的对象
String s3 = new String("1") + new String("1"); // s3是堆中的地址,可以理解为new String("11"),反正最后调用toString()(不针对JDK11)
// 上述拼接代码并没有在字符串常量池中生成"11"(参考面试题3)
// *执行该行代码时:
// JDK6:常量池会创建新对象"11"
// JDK7之后:常量池不会创建"11",而是创建一个对象指向堆中生成的对象(节省空间)
s3.intern();
String s4 = "11"; // s4使用的是上一行代码执行后在常量池生成"11"的地址(该"11"在JDK6中是常量池中的,在JDK7及之后是指向堆中创建的"11"对象的地址,s3始终是指向堆中创建的"11"对象的地址)
System.out.println(s3 == s4); // JDK7之前为false,之后为true


String s3 = new String("1") + new String("1"); // 还是相当于在堆中new String("11")
String s4 = "11"; // 在字符串常量池中会生成对象"11"
s3.intern(); // 检查到在常量池中已经有"11",不创建"11"
System.out.println(s3 == s4); // false
注意:比如
new String("a") + new String("b"),在JDK7及以上最后会调用toString()方法,该方法在字符串常量池中是没有生成"ab"的
public class Test{
String str = new String("good");
char[] ch = {'t', 'e', 's', 't'};
public void change(String str, char[] ch){
str = "test ok";
ch[0] = 'b';
}
public static void main(String[] args) {
Test t = new Test();
t.change(t.str, t.ch);
System.out.println(t.str); // good(不会变为test ok)
System.out.println(t.str); // best(第一个字符进行了修改)
}
}
String s = new String(“psj”)会创建几个对象?两个

new String(“p”) + new String(“s”)创建几个对象?JDK11中创建了4个,JDK8中创建了5个(考虑最后toString方法就是6个)
注意:这种方式在字符串常量池中最终是没有"ps"对象的,如果使用new String(“ps”)是会在常量池中有"ps"的

String s = new String("a") + new String("b"); // 拼接的方式不会在常量池中生成"ab"
String s2 = s.intern(); // JDK6会复制对象,JDK7及以上复制对象的引用
System.out.println(s == "ab");
System.out.println(s2 == "ab");
在JDK6中是true和false:

在JDK7及以上是true和true:

String x = "ab";
String s = new String("a") + new String("b"); // 还是在堆中创建"ab"对象
String s2 = s.intern(); // 因为第一行代码执行后已经在常量池中生成了"ab",所以s2直接执行常量池中已有的对象即可
System.out.println(s == "ab");
System.out.println(s2 == "ab");
在所有JDK版本都是false和true:
