谈谈Java不可变对象及我们能创建一个包含可变对象的不可变对象吗?
文章目录
谈谈不可变对象
-
可变对象:相对于不可变类,可变类创建实例后可以改变其成员变量值,开发中创建的大部分类都属于可变类。
-
不可变对象:是指一个对象的状态在对象被创建之后就不再变化。不可变对象对于缓存是非常好的选择,因为你不需要担心它的值会被更改。
-
那么对象是类实例化而来,怎么创建一个不可变类呢?
创建一个不可变类
- 一个不可变类,必须要满足以下条件:
(1)将类声明为final,所以它不能被继承;
(2)将所有的成员声明为私有的,这样就不允许直接访问这些成员;
(3)对变量不要提供setter方法;
(4)将所有可变的成员声明为final,这样只能对它们赋值一次;
(5)通过构造器初始化所有成员,进行深拷贝(deep copy);
(6)在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝;
(7)如果要修改类的状态,必须返回一个新的对象。
不可变类对于开发者来说有如下好处:
(1)易于设计,实现和使用
(2)使用过程中不容易导致出错
(3)更加的安全,可以随意地共用
(4)天然具备线程安全性,无需增加额外的同步操作
不可变对象的好处:
(1)不可变对象更容易构造,测试与使用;
(2)真正不可变对象都是线程安全的;
(3)不可变对象的使用没有副作用(没有保护性拷贝);
(4)对象变化的问题得到了避免;
(5)不可变对象的失败都是原子性的;
(6)不可变对象更容易缓存,且可以避免null引用;
(7)不可变对象可以避免时间上的耦合;
什么是不变模式
- 不变模式有两种形式:弱不变模式和强不变模式。
弱不变模式
-
弱不变模式指类实例的状态是不可改变,但是这个类的子类的实例具有可能会变化的状态,要实现弱不变模式,一个类必须满足下面条件:
(1)对象没有任何会修改对象状态的方法 ,这样一来当对象的构造函数将对象的状态初始化之后,对象的状态便不再改变;
(2)属性都是私有的,以防客户端对象直接修改内部状态; -
这个对象所引用的其他对象如果是可变的话,必须设法限制外界对这些可变对象的访问,以防止外界修改这些对象,尽量在不可变对象内部初始化这些被引用的对象,而不要在客户端初始化再传入到不可变对象内部来,如果某个可变对象必须在客户端初始化,然后再传入到不变对象里的话,就应当考虑在不可变对象初始化的时候,将这个可变对象进行拷贝。
-
弱不变模式的缺点是,一个弱不变对象的子对象可能是可变的,这个可变的子对象可能可以修改父对象的状态,从而可能允许外界修改父对象的状态。
强不变模式
- 强不变模式指一个类的实例不会改变,同时它的子类的实例也具有不可变化的状态。一个类必须首先满足弱不变模式所要求的所有条件,并且还有满足下面条件之一:
(1)类所有的方法都应当是final,这样这个类的子类不能够重写此类的方法;
(2)此类本身就是final的,那么这个类就不可能会有子类,从而也就不可能有被子类修改的问题;
不变和只读的区别
- 当一个变量是只读时,变量的值不能直接改变,但是可以在其他变量发生改变的时候改变;比如生日是不变属性,而年龄是只读的,年龄会随着时间发生变化,生日则不会变化。(用final声明的变量是只读的)
再谈可变与不可变
不可变对象一定不可变吗
-
答:“不一定的,因为不可变对象中包含有final定义的引用对象,此时引用对象的地址是不允许修改,但是引用对象的状态是允许修改的。”
-
不可变对象虽然具备不可变性,但是不是"完全不可变"的,这里打上引号是因为通过反射的手段是可以改变不可变对象的状态的。
大家看到这里可能有疑惑了,为什么既然能改变,为何还叫不可变对象?这里面大家不要误会不可变的本意,从不可变对象的意义分析能看出来对象的不可变性只是用来辅助帮助大家更简单地去编写代码,减少程序编写过程中出错的概率,这是不可变对象的初衷。如果真要靠通过反射来改变一个对象的状态,此时编写代码的人也应该会意识到此类在设计的时候就不希望其状态被更改,从而引起编写代码的人的注意。下面是通过反射方式改变不可变对象的例子:
我们能创建一个包含可变对象的不可变对象吗?
- 我们是可以创建一个包含可变对象的不可变对象的,你只需要谨慎一点,不要共享可变对象的引用就可以了,如果需要变化时,就返回原对象的一个拷贝,最常见的例子就是对象中包含一个日期对象的引用。
参考博文:https://www.cnblogs.com/dolphin0520/p/10693891.html