String类为什么要设计成不可变的
2017-10-22 10:55
162 查看
原文:http://blog.csdn.net/qingmengwuhen1/article/details/52175303
1.什么是不可变?
String不可变很简单,如下图,给一个已有字符串“abcd”第二次赋值成”abced”,不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。
2.String为什么不可变?
翻开JDK源码,java.lang.String类起手前三行,是这样写的:
[java] view plain copy print?public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** String本质是个char数组. 而且用final关键字修饰.*/ private final char value[];
有的人以为故事就这样完了,其实没有。因为虽然value是不可变的,也只是value这个引用地址不可变。挡不住Array数组是可变的事实。
也就是说Array变量只是stack上的一个引用,数据的本体结构在heap堆。String类里的value用final修饰,只是说stack里的这个叫value的引用地址不可变。没有说堆里array本身数据不可变。看这个这个例子,
[java] view plain copy print?final int[] value={1,2,3} int[] another={4,5,6}; value = another;//编译器报错,final不可变
value用final修饰,编译器不允许我把value指向堆区另一个地址。但如果直接对数组元素动手,分分钟搞定。
[html] view plain copy print?final int[] value={1,2,3}; value[2]=100;//这时候数组里已经是{1,2,100}
3.不可变有什么好处?
这个最简单的原因,就是为了安全。看下面这个场景,一个函数appendStr()在不可变的String参数后面加上一段“bbb”后返回。appendSb()负责在可变的StringBuilder后面加”bbb”。
[java] view plain copy print?Class Test{
//不可变的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 s = new String(“aaa”);
String ns = Test.appendStr(s);
System.out.println(”String aaa>>>”+s.toString());
//StringBuilder做参数
StringBuilder sb = new StringBuilder(“aaa”);
StringBuilder nsb = Test.appendSb(sb);
System.out.println(”StringBuilder aaa >>>”+sb.toString());
}
}
如果程序员不小心像上面例子里,直接在传进来的参数上加上“bbb”.因为Java对象参数传的是引用,所有可变的StringBuffer参数就被改变了。可以看到变量sb在Test.appendSb(sb)操作之后,就变成了”aaabbb”。有的时候这可能不是程序员的本意。所以String不可变的安全性就体现在这里。
再看下面这个HashSet用StrinbBuilder做元素的场景,问题就更严重了,而且更为隐蔽。
[java] view plain copy print?HashSet<StringBuilder> hs = new HashSet<StringBuilder>();
StringBuilder sb1 = new StringBuilder(“aaa”);
StringBuilder sb2 = new StringBuilder(“aaabbb”);
hs.add(sb1);
hs.add(sb2); //这时候HashSet里是{“aaa”,”aaabbb”}
StringBuilder sb3 = sb1;
sb3.append(”bbb”);//这时候HashSet里是{“aaabbb”,”aaabbb”}
System.out.println(hs);
StringBuilder型变量sb1和sb2分别指向了堆内的字面量“aaa”和”aaabbb”。把它们都插入一个HashSet。到这一步没问题。但如果后面我把变量sb3也指向sb1的地址,再改变sb3的值,因为StringBuilder没有不可变性的保护,sb3直接在原先“aaa”的地址上改。导致sb1的值也变了。这时候,HashsSet上就出现了两个相等的键值”aaabbb”。破坏了HashSet键值的唯一性。所以千万不要用可变类型做HashMap和HashSet键值。
还有一个大家都知道,就是在并发场景下,多个线程同时读一个资源,是不会引发竞态条件的。只有对资源做写操作才有危险。不可变对象不能被写,所以线程安全。
最后别忘了String另外一个字符串常量池的属性。像下面这样的字符串one和two都用字面量”something”赋值。它们其实都指向同一个内存地址。
String one = “someString”;
String two = “someString”;
这样在大量使用字符串的情况下,可以节省内存空间,提高效率。但之所以能实现这个特性,String的不可变是最基本的必要条件。要是内存里字符串内容能改来改去,这么做就完全没有意义了。
1.什么是不可变?
String不可变很简单,如下图,给一个已有字符串“abcd”第二次赋值成”abced”,不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。
2.String为什么不可变?
翻开JDK源码,java.lang.String类起手前三行,是这样写的:
[java] view plain copy print?public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** String本质是个char数组. 而且用final关键字修饰.*/ private final char value[];
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** String本质是个char数组. 而且用final关键字修饰.*/ private final char value[];首先String类是用final关键字修饰,这说明String不可继承。再看下面,String类的主力成员字段value是个char[]数组,而且是用final修饰的。final修饰的字段创建以后就不可改变。
有的人以为故事就这样完了,其实没有。因为虽然value是不可变的,也只是value这个引用地址不可变。挡不住Array数组是可变的事实。
也就是说Array变量只是stack上的一个引用,数据的本体结构在heap堆。String类里的value用final修饰,只是说stack里的这个叫value的引用地址不可变。没有说堆里array本身数据不可变。看这个这个例子,
[java] view plain copy print?final int[] value={1,2,3} int[] another={4,5,6}; value = another;//编译器报错,final不可变
final int[] value={1,2,3} int[] another={4,5,6}; value = another;//编译器报错,final不可变
value用final修饰,编译器不允许我把value指向堆区另一个地址。但如果直接对数组元素动手,分分钟搞定。
[html] view plain copy print?final int[] value={1,2,3}; value[2]=100;//这时候数组里已经是{1,2,100}
final int[] value={1,2,3}; value[2]=100;//这时候数组里已经是{1,2,100}所以String是不可变,关键是因为SUN公司的工程师,在后面所有String的方法里很小心地没有去动Array里的元素,没有暴露内部成员字段。private final char value[]这一句里,private的私有访问权限的作用都比final大。而且设计师还很小心地反整个String设计成final禁止继承,避免被其他人继承后破坏。所以String是不可变的关键在于底层的实现,而不是一个final。考验的是工程师构造数据类型,封装数据的功力。
3.不可变有什么好处?
这个最简单的原因,就是为了安全。看下面这个场景,一个函数appendStr()在不可变的String参数后面加上一段“bbb”后返回。appendSb()负责在可变的StringBuilder后面加”bbb”。
[java] view plain copy print?Class Test{
//不可变的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 s = new String(“aaa”);
String ns = Test.appendStr(s);
System.out.println(”String aaa>>>”+s.toString());
//StringBuilder做参数
StringBuilder sb = new StringBuilder(“aaa”);
StringBuilder nsb = Test.appendSb(sb);
System.out.println(”StringBuilder aaa >>>”+sb.toString());
}
}
Class Test{ //不可变的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 s = new String("aaa"); String ns = Test.appendStr(s); System.out.println("String aaa>>>"+s.toString()); //StringBuilder做参数 StringBuilder sb = new StringBuilder("aaa"); StringBuilder nsb = Test.appendSb(sb); System.out.println("StringBuilder aaa >>>"+sb.toString()); } }
如果程序员不小心像上面例子里,直接在传进来的参数上加上“bbb”.因为Java对象参数传的是引用,所有可变的StringBuffer参数就被改变了。可以看到变量sb在Test.appendSb(sb)操作之后,就变成了”aaabbb”。有的时候这可能不是程序员的本意。所以String不可变的安全性就体现在这里。
再看下面这个HashSet用StrinbBuilder做元素的场景,问题就更严重了,而且更为隐蔽。
[java] view plain copy print?HashSet<StringBuilder> hs = new HashSet<StringBuilder>();
StringBuilder sb1 = new StringBuilder(“aaa”);
StringBuilder sb2 = new StringBuilder(“aaabbb”);
hs.add(sb1);
hs.add(sb2); //这时候HashSet里是{“aaa”,”aaabbb”}
StringBuilder sb3 = sb1;
sb3.append(”bbb”);//这时候HashSet里是{“aaabbb”,”aaabbb”}
System.out.println(hs);
HashSet<StringBuilder> hs = new HashSet<StringBuilder>(); StringBuilder sb1 = new StringBuilder("aaa"); StringBuilder sb2 = new StringBuilder("aaabbb"); hs.add(sb1); hs.add(sb2); //这时候HashSet里是{"aaa","aaabbb"} StringBuilder sb3 = sb1; sb3.append("bbb");//这时候HashSet里是{"aaabbb","aaabbb"} System.out.println(hs);
StringBuilder型变量sb1和sb2分别指向了堆内的字面量“aaa”和”aaabbb”。把它们都插入一个HashSet。到这一步没问题。但如果后面我把变量sb3也指向sb1的地址,再改变sb3的值,因为StringBuilder没有不可变性的保护,sb3直接在原先“aaa”的地址上改。导致sb1的值也变了。这时候,HashsSet上就出现了两个相等的键值”aaabbb”。破坏了HashSet键值的唯一性。所以千万不要用可变类型做HashMap和HashSet键值。
还有一个大家都知道,就是在并发场景下,多个线程同时读一个资源,是不会引发竞态条件的。只有对资源做写操作才有危险。不可变对象不能被写,所以线程安全。
最后别忘了String另外一个字符串常量池的属性。像下面这样的字符串one和two都用字面量”something”赋值。它们其实都指向同一个内存地址。
String one = “someString”;
String two = “someString”;
这样在大量使用字符串的情况下,可以节省内存空间,提高效率。但之所以能实现这个特性,String的不可变是最基本的必要条件。要是内存里字符串内容能改来改去,这么做就完全没有意义了。
相关文章推荐
- java中String类为什么要设计成不可变的
- java中String类为什么要设计成不可变的
- java中String类为什么要设计成不可变的
- 为什么String要设计成不可变的?
- 为什么String类是不可变的?
- 为什么String类是不可变的?
- Java的string类为什么是不可变的
- String的内存模型,为什么String被设计成不可变的
- 为什么String类是不可变的?
- [2017-09-04]Abp系列——为什么值对象必须设计成不可变的
- 为什么String被设计为不可变?是否真的不可变?
- 为什么要把Java字符串设计为不可变的
- 【知乎】String类为什么要设计成final?
- 为什么String要设计成不可变的?
- 为什么String类是不可变的
- 为什么jdk中把String类设计成final?
- 为什么String要设计成不可变的
- Java的string类为什么是不可变的
- 为什么String类是不可变的?
- 为什么String类是不可变的