String类的细节

76 阅读3分钟

String类为什么不可变

  • 在string类内部,是使用final char[] value来存储字符的,value不能再指向新的char[]数组
  • 虽然value不能执行新的数组,但是value的内容是可以改变的。关键的一点就是String内部并没有提供改变value内容的方法
  • String类使用final修饰,所以不能用子类重写String的方法

String类为什么设计为不可变类

  • 线程安全
  • 缓存hash值,如果在map中使用String作为key,String的hash值只需要计算一次即可,因为String不可变。大大提高效率
  • 字符串常量池的设计(有点模糊)
    // 常量池中创建"b"对象
    String a = "b";
    // b指向的就是 a指向的字符串对象
    String b = "b";
    // 如果字符串可变,那么b也会跟着改变;并不想改变b,常量池的存在影响了正常的程序编写
    // 所以常量池需要String不可变
    a = "c"; 
    

new String创建了几个对象

String a = new String("a");
  • 如果字符串常量池中不存在"a"的引用,那么会在堆中创建两个对象,并且在常量池中存储字符串"a"的引用
    • 一个是"a"字符串
    • 一个是String对象,用"a"来初始化String对象
  • 如果字符串常量池中存在"a"的引用,只创建一个String对象

String intern方法

  • 在jdk8中,intern方法判断字符串常量池中是否存在该字符串的引用,如果存在,返回引用;如果不存在,就保存该字符串的引用,并返回
String b = new String("f"); // 创建两个对象,并且在字符串常量池中生成一个"f"的引用
String a = "f";
String c = b.intern();
System.out.println(a==b); // false
System.out.println(a==c); // true
  • b存储的是String对象地址
  • a存储的是字符串"f"地址
  • intern方法返回字符串的引用(地址),所以c存储的是字符串"f"地址
String s = new String("abc") + new String("acd");
System.out.println(s.intern() == s); // true
System.out.println(s == "abcacd"); // true
  • 这里跟上面是有区别的,在两个String相加之后,常量池中并没有"adbacd"这个字符串的引用!
19: ldc           #7                  // String abc 加载abc字符串进常量池
21: invokespecial #8                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
24: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
27: new           #6                  // class java/lang/String
30: dup
31: ldc           #10                 // String acd 加载acd字符串进常量池
33: invokespecial #8                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
36: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
39: invokevirtual #11                 // Method java/lang/StringBuilder.toString: ()Ljava/lang/String; 利用StringBuilder的toString方法返回String,但是常量池中没有"adbacd"这个引用

  • 调用s.intern()方法返回的是s指向的String对象,因为String对象指向了字符串"abcacd",String对象就是字符串"abcacd"的引用,所以s.intern()==s

把代码改变一下顺序:

String s = new String("abc") + new String("acd");
System.out.println(s == "abcacd"); // false
System.out.println(s.intern() == s);  // false
  • s == "abcacd"比较时,由于字符串常量池中没有"abcacd",所以需要将"abcacd"引用存储到字符串常量池中,此时字符串常量池的存储的引用就和s指向的地址不一样了

学习的是一个说服自己的过程,虽然上面的过程真的很牵强,如果看到这篇文章觉得有更好的解释的话希望能给出链接或者是评论区点出错误