一、不可变类和不可变对象
创建一个一旦其内容就不能在改变的对象,称其为一个不可变对象(immutable object),而它的类称为不可变类(immutable class)。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
private int hash;
可以看到String的本质是一个char数组,是对字符串数组的封装,并且是被final修饰的,创建后不可改变。
二、String类不可变性的好处
Java中将String设计成不可变的是综合考虑到各种因素的结果,想要理解这个问题,需要综合内存,同步,数据结构以及安全等方面的考虑
1、字符串常量池
在Java中,由于会大量的使用String常量,如果每一次声明一个String都创建一个String对象,那将会造成极大的空间资源的浪费。Java提出了String pool的概念,在堆内存中开辟一块存储空间String pool,当初始化一个String变量时,如果该字符串已经存在了,就不会去创建一个新的字符串变量,而是会返回已经存在了的字符串的引用。
String str1 = "abc";
String str2 = "abc";
String a = "ab";
String b = "c";
String str3 = a+b;
String str4 = "abcd";
System.out.println("str1地址:"+System.identityHashCode(str1));
System.out.println("str2地址:"+System.identityHashCode(str2));
System.out.println("str3地址:"+System.identityHashCode(str3));
System.out.println("str4地址:"+System.identityHashCode(str4));
System.out.println("str1.hashCode():"+str1.hashCode());
System.out.println("str2.hashCode():"+str2.hashCode());
System.out.println("str3.hashCode():"+str3.hashCode());
System.out.println("a.hashCode():"+a.hashCode());
System.out.println("str1 == str2:"+(str1 == str2));
System.out.println("str1.equals(str2):"+str1.equals(str2));
System.out.println("str3 == str1:"+(str3 == str1));
System.out.println("str3.equals(str1):"+str3.equals(str1));
str1 = "abcd";
System.out.println("更改后的str1地址:"+System.identityHashCode(str1));
System.out.println("更改后的str1.hashCode():"+str1.hashCode());
System.out.println("str1 == str4:"+(str1 == str4));
System.out.println("str1.equals(str4):"+str1.equals(str4));
str2 += "d";
System.out.println("更改后的str2地址:"+System.identityHashCode(str2));
System.out.println("更改后的str2.hashCode():"+str2.hashCode());
System.out.println("str1 == str2:"+(str1 == str2));
System.out.println("str1.equals(str2):"+str1.equals(str2));
输出:
str1地址:23934342
str2地址:23934342
str3地址:22307196
str4地址:10568834
str1.hashCode():96354
str2.hashCode():96354
str3.hashCode():96354
a.hashCode():3105
str1 == str2:true
str1.equals(str2):true
str3 == str1:false
str3.equals(str1):true
更改后的str1地址:10568834
更改后的str1.hashCode():2987074
str1 == str4:true
str1.equals(str4):true
更改后的str2地址:21029277
更改后的str2.hashCode():2987074
str1 == str2:false
str1.equals(str2):true
这个例子可以看到我们想要的结果:如果字符串是可变的,某一个字符串变量改变了其值,那么其指向的变量的值也会改变,String pool将不能够实现!
str1和str2初始化都是"abc",abc在堆中存在,所以地址是相同的,他们的hashcode()也是相同的。所以str1 == str2:true ;str1.equals(str2):true他们的比较都是true;
- “==” 是判断两个变量是否指向同一个地方,即存储位置。也就是说是否引用同一个变量。
- equals() 是判断两个String类型字符串的内容是否一样。
但是当str3为字符串“ab”+“c”时,是一个新的地址,所以str3 == str1:false,(虽然str3.hashCode()和str1.hashCode()相同,但是地址不同),下面同理。
2、使多线程安全
public class Stringtest {
public String appendStr(String s){
s += "efg";
return s;
}
public StringBuilder appendSb(StringBuilder sb){
sb.append("efg");
return sb;
}
public static void main(String[] args) {
Stringtest stringtest = new Stringtest();
String str = "abcefg";
System.out.println("str地址:"+System.identityHashCode(str));
String str1 = new String("abc");
StringBuilder str2 = new StringBuilder("abc");
System.out.println("str1地址:"+System.identityHashCode(str1));
System.out.println("str2地址:"+System.identityHashCode(str2));
System.out.println("str1 == str2:"+(str1 == str2.toString()));
System.out.println("str1.equals(str2.toString()):"+str1.equals(str2.toString()));
String newstr1 = stringtest.appendStr(str1);
System.out.println("newstr1地址:"+System.identityHashCode(newstr1));
StringBuilder newstr2 = stringtest.appendSb(str2);
System.out.println("newstr2地址:"+System.identityHashCode(newstr2));
}
}
输出:
str地址:23934342
str1地址:22307196
str2地址:10568834
str1 == str2:false
str1.equals(str2.toString()):true
newstr1地址:21029277
newstr2地址:10568834
发现当对String字符串进行修改,地址改变,但是当对StringBuilder字符串进行修改,字符串的地址并没有改变。因为Java对象参数传的是引用,所有可变的StringBuffer参数就被改变了。可以看到变量str2在appendSb(str2)操作之后,就变成了"abcefg"。有的时候这可能不是程序员的本意。所以String不可变的安全性就体现在这里。
3、避免安全问题
在网络连接和数据库连接中字符串常常作为参数,例如,网络连接地址URL,文件路径path,反射机制所需要的String参数。其不可变性可以保证连接的安全性。如果字符串是可变的,黑客就有可能改变字符串指向对象的值,那么会引起很严重的安全问题。 因为String是不可变的,所以它的值是不可改变的。但由于String不可变,也就没有任何方式能修改字符串的值,每一次修改都将产生新的字符串,如果使用char[]来保存密码,仍然能够将其中所有的元素设置为空和清零,也不会被放入字符串缓存池中,用字符串数组来保存密码会更好。
4、加快字符串处理速度
由于String是不可变的,保证了hashcode的唯一性,于是在创建对象时其hashcode就可以放心的缓存了,不需要重新计算。这也就是Map喜欢将String作为Key的原因,处理速度要快过其它的键对象。所以HashMap中的键往往都使用String。
|