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