String:String类型为何不可变

在学习Java的过程当中,咱们会被告知 String 被设计成不可变的类型。为何 String 会被 Java 开发者有如此特殊的对待?他们的设计意图和设计理念究竟是什么?所以,我带着如下三个问题,对
String 进行剖析:java

  • String 真的不可变吗?数据库

  • 为何会将 String 设计为不可变?编程

  • 如何经过技术实现实现 String 不可变 ?数组

String 真的不可变?

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
    
    //other codes
}

String 的底层实现是依靠 char[] 数组,既然依靠的是基础类型变量,那么他必定是可变的, String 之因此不可变,是由于 Java 的开发者经过技术实现,隔绝了使用者对 String 的底层数据的操做。可是,咱们能够同反射的机制,操做 String 的底层,检验其不可变的猜测。安全

反射的方式操做 String :并发

//建立字符串"Hello World", 并赋给引用s  
        String s = "Hello World";   
          
        System.out.println("s = " + s);    // Hello World  
          
        //获取String类中的value字段  
        Field valueFieldOfString = String.class.getDeclaredField("value");  
          
        //改变value属性的访问权限  
        valueFieldOfString.setAccessible(true);  
          
        //获取s对象上的value属性的值  
        char[] value = (char[]) valueFieldOfString.get(s);  
          
        //改变value所引用的数组中的第5个字符  
        value[5] = '_';  
          
        System.out.println("s = " + s);    //Hello_World

经过两次字符串的输出,咱们能够看到,String 被改变了,可是在代码里,几乎不会使用反射的机制去操做 String 字符串,因此,咱们会认为 String 类型是不可变的。app


为何会将 String 设计为不可变

  • 安全socket

    • 引起安全问题,譬如,数据库的用户名、密码都是以字符串的形式传入来得到数据库的链接,或者在socket编程中,主机名和端口都是以字符串的形式传入。由于字符串是不可变的,因此它的值是不可改变的,不然黑客们能够钻到空子,改变字符串指向的对象的值,形成安全漏洞性能

    • 保证线程安全,在并发场景下,多个线程同时读写资源时,会引竞态条件,因为 String 是不可变的,不会引起线程的问题而保证了线程

    • HashCode,当 String 被建立出来的时候,hashcode也会随之被缓存,hashcode的计算与value有关,若 String 可变,那么 hashcode 也会随之变化,针对于 Map、Set 等容器,他们的键值须要保证惟一性和一致性,所以,String 的不可变性使其比其余对象更适合当容器的键值。

  • 性能

    • 当字符串是不可变时,字符串常量池才有意义。字符串常量池的出现,能够减小建立相同字面量的字符串,让不一样的引用指向池中同一个字符串,为运行时节约不少的堆内存。若字符串可变,字符串常量池失去意义,基于常量池的String.intern()方法也失效,每次建立新的 String 将在堆内开辟出新的空间,占据更多的内存

实例代码:

String 的不可变性:

public static String appendStr(String s){
        s+="bbb";
        return s;
    }

    //可变的StringBuilder
    public static StringBuilder appendSb(StringBuilder sb){
        return sb.append("bbb");
    }

    public static void main(String[] args){
        //String作参数
        String s=new String("aaa");
        String ns=Test.appendStr(s);
        System.out.println("String aaa >>> "+s.toString()); // aaa

        //StringBuilder作参数
        StringBuilder sb=new StringBuilder("aaa");
        StringBuilder nsb=Test.appendSb(sb);
        System.out.println("StringBuilder aaa >>> "+sb.toString()); // aaabbb
    }

String 不可变的技术实现

打开JDK的源码:

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
    
    //other codes
}
  • String 类由关键字 final 修饰,说明该类不可继承

  • char value[] 属性也被 final 所修饰,说明 value 的引用在建立以后,就不能被改变

以上两点并不能彻底实现 String 不可变 ,缘由在于:

final int[] value={1,2,3}
      int[] another={4,5,6};
value=another;    // 编译器报错,final不可变

value 被 final 修饰,只能保证引用不被改变,可是 value 所指向的堆中的数组,才是真实的数据,只要可以操做堆中的数组,依旧能改变数据。【解释:String其实是可变的】

final int[] value={1,2,3};
value[2]=100;  //这时候数组里已是{1,2,100}
  • 全部的成员属性均被 private 关键字所修饰

为了实现 String 不可变,关键在于Java的开发者在设计和开发 String 的过程当中,没有暴露任何的内部成员,与此同时 API 的设计是均没有操做 value 的值 , 而是采用 new String() 的方式返回新的字符串,保证了 String 的不可变。

JDK String API 源码:

public static String valueOf(char c) {
        char data[] = {c};
        return new String(data, true);  //采用 new String() 的方式返回新的字符串
    }
    
    

    public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);  //采用 new String() 的方式返回新的字符串
    }

整个String设成final禁止继承,避免被其余人继承后破坏。因此String是不可变的关键都在底层的实现,而不是一个final。考验的是工程师构造数据类型,封装数据的功力。

String s = "abcd";
s = "abcdel";

String 不可变性的图示:

clipboard.png

相关文章
相关标签/搜索