java.lang.String为什么是final的?

设计者将String设计成不可变,主要就是为了安全。

String str1 = new String(“张三”); String str2 = new String(“张三”), 如果常量池里没有 “张三”,那么创建str1的时候,常量池会创建"张三",假设地址是0xff,并将str1 指向 0xff,创建 str2 的时候,会直接将 str2 指向 常量池的"张三"。str1 和 str2 指向了同一个地址,多引用,一个常量,节省了空间。
这个时候把 str1 的 值改为了 “李四”,那么 str2 的值是多少呢?假设字符串是可变的,那么 str2 的值也是"李四"了。java字符串应用极多,url,密码等等都是字符串,例如:str1 = “192.168.10.102:8080”;str2 = “192.168.10.102:8080”;修改 str1 的值,就会对 用到 str2 的地方构成安全隐患。比如类加载,数据库地址端口密码等等,如果可变,就会给黑客可乘之机,更改字符串,造成严重破坏。正是字符串的不可变性,完美解决了这个问题,当更改了 str1 的值,str1 会指向新的 字符串,不会对 str2 的值 进行更改。同理,多线程操作同一个字符串,也是如此,来保证安全。

java.lang.String为什么是final的?

图一,str1 和 str2 指向同一个字符串

java.lang.String为什么是final的?

图二,修改str1的值

另外,创建一个字符串的时候,它的hashcode就被确定了,不需要二次计算,所以Map的键往往都是字符串,因为字符串的处理速度要快过其它类型的键。
以上是String为什么设计成不可变的原因,那么String 不可变是怎么实现的呢。首先看看String类的前三行源码。如图:
java.lang.String为什么是final的?

图三,String类源码

String类本身是由final修饰的,意味着最终的,不可被继承,也就没有办法重写String类的方法,保证了字符串是原滋原味的String类,不是什么子类。成员属性 value[] ,字符串的值就是存放在这个 数组里面,final char value[],value属性一旦赋值,就不可以再指向其他的地址了。那么问题来了,既然是数组,可不可以通过改变数组内元素的值,来破坏 字符串不可变性呢?答案是不可以,value 属性被 private 修饰,其他类无法直接访问这个 属性,更不可能更改它里面的元素了。
那么有人就会问了,字符串常用操作,拼串,转换大小写,subString这些操作都是更改字符串的值,明明我更改了,为什么还说它不可变呢?来,我们随便看一个String类里,可以更改值的方法。
java.lang.String为什么是final的?

图四,字符串切串源码

看一下return,如果切点是0,也就是字符串起始位,那么等于没有切串,字符串未作修改,返回它自己,如果不是0,那么返回的是一个new String(),新的String对象,举个例子,
String name = new String(“哈哈”);
name = name.subString(1);
执行完subString()方法后,name已经是一个新的对象了。
综述,String不可变性由三个地方保证,第一,String类方法,修改字符串值后,方法一律返回new String(),保证其他指向这个字符串的对象不受影响;第二,String类是final的,没有子类,方法不可以被重写,保证了字符串的操作都是按预先设计好的模式走的;第三,存放字符串值的 value[] 数组,是final 且 private的,不可以更改,不可以被其他类直接访问,保证了value值的不变性和安全性。
看到这里,对于java.lang.String有没有一个大概的认知了呢?
字符串是不是绝对不可变呢?不是的,可以通过反射去破坏它的不可变性,反射属于jvm提供的外挂,这里不做赘述,下面上代码,有兴趣的小伙伴自己去研究一下吧。
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
String className = “java.lang.String”;
Class clazz = Class.forName(className);
Field field = clazz.getDeclaredField(“value”);
field.setAccessible(true);
String aa = “aa”;
System.out.println(aa);
Object o = field.get(aa);
char[] arr = (char[]) o;
System.out.println(System.identityHashCode(arr));
arr[0] = ‘b’;
System.out.println(“aa”);
}
java.lang.String为什么是final的?