我的理解
比如我自己写的类:如Person
public class Person {
public static void main(String[] args) {
Person p1 = new Person();
p1.setAge(10);
Person p2 = p1;
p2.setAge(12);
System.out.println(p1.getAge());
}
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
结果是12,说明p1虽然没直接改变但是p1年龄还是变了,因为p2=p1使得他们指向同一个引用地址,p2改变(p2没有指向新的引用地址的话)p1也跟着变。
而如果是String类
public static void main(String[] args) {
String s1="aaa";
String s2=s1;
s2="bbb";
System.out.println(s1);
}
结果是"aaa",说明s2改变是指向了新的引用地址,不影响s1。
String真正不可变的原因
- 保存字符串的数组value被private和final修饰,并且String类没有提供修改value的方法(比如Person类setAge()就是修改Person对象的方法),说明value不能被改变,String对象变的话(如s="ccc",s.replace("ccc","ddd"),s.substring(0,1))也是s指向了新的引用地址,而不是改变了value。
- String类被final修饰,不能被继承,避免了子类破坏String的不可变特点
String真就不可改变吗
答案是否定的。
我们可以利用反射机制改变value
public static void main(String[] args) {
String str = "aaa";
System.out.println("str = " + str + "\t" + "hashcode=" + str.hashCode());
try {
Field valueField = String.class.getDeclaredField("value");
//暴力反射!
valueField.setAccessible(true);
byte[] valueCharArr = (byte[]) valueField.get(str);
//改变!
valueCharArr[0] = 'b';
System.out.println("str = " + str + "\t" + "hashcode=" + str.hashCode());
} catch (NoSuchFieldException | SecurityException
| IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
结果:我们改变了value!
为什么String要设计成不可变的
-
不可变对象天生就是线程安全的:因为不可变对象不能被改变,所以他们可以自由地在多个线程之间共享。不需要任何同步处理。
-
便于实现字符串池:在Java中,由于会大量的使用String常量,如果每一次声明一个String都创建一个String对象,那将会造成极大的空间资源的浪费。Java提出了String pool的概念,在堆中开辟一块存储空间String pool,当初始化一个String变量时,如果该字符串已经存在了,就不会去创建一个新的字符串变量,而是会返回已经存在了的字符串的引用。
-
避免安全问题:在网络连接和数据库连接中字符串常常作为参数,例如,网络连接地址URL,文件路径path,反射机制所需要的String参数。其不可变性可以保证连接的安全性。如果字符串是可变的,黑客就有可能改变字符串指向对象的值,那么会引起很严重的安全问题。
-
加快字符串处理速度由于String是不可变的,保证了hashcode的唯一性,于是在创建对象时其hashcode就可以放心的缓存了,不需要重新计算。这也就是Map喜欢将String作为Key的原因,处理速度要快过其它的键对象。所以HashMap中的键往往都使用String。
在String类的定义中有如下代码:
private int hash;//用来缓存HashCode
总体来说,String不可变的原因要包括 设计考虑,效率优化,以及安全性这三大方面。