1、什么是不可变?
java语言要求不可变要遵循下面5点:
(1)、类内部所有字段都是final修饰的
(2)、类内部所有字段都是私有的,即private修饰的
(3)、类不能被继承或拓展
(4)、类不能对外提供能够修改内部状态的方法,setter方法也不行
(5)、类内部的字段如果是引用,也就是说可以指向可变对象,程序员不能获取这个引用
String就包含了上面5个特性。(第一点在严格意义上来说,并不是所有的字段都要是final修饰的,比如String中就包含了hash这个属性并没有由final修饰,这是因为String惰性计算hashCode存储在hash中(这是由于利用了String中其他的final类型的属性来保证每次hashCode的计算结果一样))。
ps:Integer等自动装箱类和String一样,但也有所不同,Integer只在值[-128,127]直接会直接去缓存中去读取。
2、String怎么被设计成不可变的?
public class Main01 {
public static void main(String[] args) {
String s = "s";
System.out.println(s);
s="d";
System.out.println(s);
}
这一段代码的输出结果:
不是说String是不可变的吗,怎么s的值变了?下面一个图能很好解释这个问题:
实际上是指向的地址变了,但是原来的指向地址的值并没有变,所以是满足String的不可变的。
下面看一下String的源码:
public final class String
implements Serializable, Comparable, CharSequence
{
private final char value[];
private int hash;
}
我们的String对象就是一个个字符存储在value数组里的,但是value没有set方法(value是由final修饰的),所以看着就是不可变的。数组也是引用传递
所以实际上起作用的是value,是value改变了引用。
3、为什么要将String设计成不可变的
(1)、提高字符串处理速度。在一个java项目中,String的使用是最多的,这就涉及到了增删改查,每次增删改查前jvm都会就检查String的安全性,就是通过hashCode,如果设计成了不可变的,就保证了每次的hashCode是唯一的。
(2)、节省空间。把字符串缓存起来,能够节省大量的堆空间,因为不同的字符串变量引用的是字符串常量池中的同一个对象。字符串常量池是java虚拟机用来存储字符串的特殊区域,由于String是不可变的,所以java虚拟机可以为同一个字符串只创建一个字符串副本,大大的节省了空间。
(3)、网络安全性。在网络连接和数据库连接中字符串常常作为参数,例如,网络连接地址URL,文件路径path,反射机制所需要的String参数。其不可变性可以保证连接的安全性。如果字符串是可变的,黑客就有可能改变字符串指向对象的值,那么会引起很严重的安全问题。
因为String是不可变的,所以它的值是不可改变的。但由于String不可变,也就没有任何方式能修改字符串的值,每一次修改都将产生新的字符串,如果使用char[]来保存密码,仍然能够将其中所有的元素设置为空和清零,也不会被放入字符串缓存池中,用字符串数组来保存密码会更好
(4)、线程安全。由于字符串是不可变的,因此可以在多线程之间共享,如果一个线程把字符串的值修改为另外一个,那么就会在字符串常量池中创建另外一个字符串,原有的字符串仍然会保持不变。