PinkHello
做一个快乐的程序猿
03 String为什么设计成final

String源码剖析


public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    
    /** The value is used for character storage. */
    private final char value[];
    
    /** Cache the hash code for the string */
    private int hash; // Default to 0 
    
    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;   
    
    /**
     * Class String is special cased within the Serialization Stream Protocol.
     *
     * A String instance is written into an ObjectOutputStream according to
     * <a href="{@docRoot}/../platform/serialization/spec/output.html">
     * Object Serialization Specification, Section 6.2, "Stream Elements"</a>
     */
    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];    
}

String 如何保证 不可变的呢?

字符数组使用了 final 修饰,这也只是表示了 字符数组的引用地址不可变,并不代表内容不可变。 其使用 private 修饰,外部没有入口达到变动,从而保证了 String 的不可变性

为什么保证 Stringfinal 的呢?特点:

  • 因为只有保证 Stringfinal 的呢 只有当字符串不可变的,字符串池才可能实现;字符串池的实现可以节省很多的 Heap空间,因为不同的字符串变量都指向池中的同一个字符串.
  • 假如字符串可变,那么String interning 不能实现,那么变量改变了这个字符串的值,那么其他指向这个值的变量的值也改变了。 (安全问题来了,用户名密码,端口、IP等)
  • 字符串不可变,多线程安全,同一个字符串实例可以被对歌线程共享,不需要考虑同步问题
  • 上面看出,在字符串不可变的情况下,创建的时候 hashcode也被缓存了,不需要重新计算。一部分性能问题可以很好的选择字符串

StringJVM 层理解 拓展

JVM 层面有 虚拟机栈、本地方法栈、堆、程序计数器、元数据区(方法区) 运行时数据区

  • 字符串创建形式
String s1 = "1";
String s2 = new String("1");
  • 编译期间 “1” 作为常量进入字符串常量池(这时候是静态常量池);
  • 当编译 s1 时候,将 s1 推进局部变量区(栈帧内部); 先判断 “1” 在字符串常量池在不在,不存在的话创建常量 “1” 加入常量池,并直接将 s1 指向字符串常量池的对象 “1” 地址;
  • 当代码运行 s2 时候,使用的 new 字段,JVM 先检查字符串常量在常量池存不存在,如果已经存在,直接在堆中复制改对象的副本,并且将 s2 指向堆中的刚刚创建的对象地址。 如果不存在,则会实例化该字符串 “1” 并且将其放到常量池中(这时候是运行时常量池),然后在堆中复制刚刚加入常量池对象的副本创建出新的对象,并将 s2 指向它
  • ‘+‘号连接的字符串
String s1 = "1"+"2"+"3";
  • 编译期间就能确定,直接作为 “123” 的常量进入常量池;
String s2 = "1"+"2"+new String("3")+"4";
  • 当 ‘+’ 中间有变量时候,也只能在运行期才能确定,但是在编译期间会尽量的将字符串常量连接起来,形成新的字符串常量;
  • 反编译后 String s2 = new StringBuilder("12").append(new String("3"")).append("4").toString();
  • 也就是说用 ‘+’ 连接中间有变量的时候,“13”, “3”, “4” 在常量池中,在堆中有 StringBuilder("1234"), String("4"),以及toString后产生的String对象
String s3 = new String("3") + new String("3");
  • “3” 在常量池中(编译的时候)
  • 运行时,在堆中产生 “3” 的副本两个 String 对象,并产生 StringBuilder("11") 对象以及 toStringString 对象

最后修改于 2019-02-12