不可变类(Immutable Objects):当类的实例一经建立,其内容便不可改变,即没法修改其成员变量。html
可变类(Mutable Objects):类的实例建立后,能够修改其内容。java
Java 中八个基本类型的包装类和 String 类都属于不可变类,而其余的大多数类都属于可变类。缓存
须要特别注意的是,不可变类的不可变是指该类的实例不可变而非指向该实例的引用的不可变。安全
String s = "abc"; System.out.println("s:" + s); // 输出s:abc s = "xyz"; System.out.println("s:" + s); // 输出s:xyz
以上代码显示,不可变类 String 貌似是能够改变值的,但实际上并非。变量 s 只是一个指向 String 类的实例的引用,存储的是实例对象在内存中的地址。代码中第三行的 “改变” 其实是新实例化了一个 String 对象,并将 s 的指向修改到新对象上,而原来的对象在内存中并未发生变化,只是少了一个指向它的引用,而且在将来被垃圾回收前它都将保持不变。多线程
public class Immutable { public static void main(String[] args) { String str = new String("abc"); String str2 = str; System.out.println(str == str2); // true str2 = "cba"; System.out.println(str == str2); // false System.out.println(str == row(str)); // true System.out.println(str == other(str)); // false } static private String row(String s){ return s; } static private String other(String s){ s="xyz"; //此处形参 s 指向了新的String对象,引用的地址发生变化 return s; } }
如此咱们看到,对于不可变类的对象,都是经过新建立一个对象并将引用指向新对象来实现变化的。性能
一般,使用关键字 final 修饰的字段初始化后是不可变的,而这种不可变就是指引用的不可变。具体就是该引用所指对象的内存地址是不可变的,但并不是该对象不可变。若是该对象也不可变,那么该对象就是不可变类的一个实例。this
public class Immutable { public static void main(String[] args) { Immutable immutable = new Immutable(); final Inner inner = immutable.new Inner(); inner.value = 123; // 实例可变 // 下面语句编译错误,inner 是final的,没法让它指向新的对象(改变指向地址) // inner = it.new Inner(); Inner inner2 = inner; // 复制了一份引用,inner和inner2指向同一个对象 System.out.println(inner); // 将调用 toString 方法输出对象内存地址 System.out.println(inner2); // inner和inner2具备相同的地址 System.out.println(inner.value); // 输出 123 System.out.println(inner2.value); // 输出123 inner2.value = 321; System.out.println(inner); // 输出321 } class Inner{ private int value; } }
immutable对象的状态在建立以后就不能发生改变,任何对它的改变都应该产生一个新的对象。spa
所以,一个不可变类的定义应当具有如下特征:线程
下面是一个示例:设计
public final class ImmutableDemo { private final int[] myArray; public ImmutableDemo(int[] array) { // this.myArray = array; // 错误! this.myArray = array.clone(); // 正确 } public int[] get(){ return myArray.clone(); } }
上例中错误的方法不能保证不可变性,myArray 和形参 array 指向同一块内存地址,用户能够在 ImmutableDemo 实例以外经过修改 array 对象的值来改变实例内部 myArray 的值。正确的作法是经过深拷贝将 array 的值传递给 myArray 。一样, getter 方法中不能直接返回对象自己,而应该是克隆对象并返回对象的拷贝,这种作法避免了对象外泄,防止经过 getter 得到内部可变成员对象后对成员变量直接操做,致使成员变量发生改变。
对于不可变类,String 是一个典型例子,看看它的源码也有助于咱们设计不可变类。
不可变类有两个主要有点,效率和安全。
当一个对象是不可变的,那么须要拷贝这个对象的内容时,就不用复制它的自己而只是复制它的地址,复制地址(一般一个指针的大小)只须要很小的内存空间,具备很是高的效率。同时,对于引用该对象的其余变量也不会形成影响。
此外,不变性保证了hashCode 的惟一性,所以能够放心地进行缓存而没必要每次从新计算新的哈希码。而哈希码被频繁地使用, 好比在hashMap 等容器中。将hashCode 缓存能够提升以不变类实例为key的容器的性能。
线程安全
在多线程状况下,一个可变对象的值极可能被其余进程改变,这样会形成不可预期的结果,而使用不可变对象就能够避免这种状况同时省去了同步加锁等过程,所以不可变类是线程安全的。
固然,不可变类也有缺点:不可变类的每一次“改变”都会产生新的对象,所以在使用中不可避免的会产生不少垃圾。
更多关于深度拷贝的知识,参阅这篇:java对象深复制、浅复制(深拷贝、浅拷贝)的理解