一.什么是可变,什么是不可变?
可变与不可变的讨论我认为可以从以下两个角度来思考:
- 一种观点是:可变与不可变的区分在于事物(ADT)本身是否能够改变
- 另一种观点是:可变与不可变的区分在于事物(ADT)是否提供了可以改变自身的方法
这两种观点之间也有着一些联系。
比如,单独的一个ArrayList,由于它本身具有add(),remove()等方法,我们认为其是可变的;但是如果创建一个新类,里面只有一个私有字段ArrayList,是private的,并且不存在任何的表示泄露,甚至没有任何一个方法,虽然ArrayList本身是可变的,但是我们并没有暴露任何可以改变它的方法,那么对我们来说,它似乎也是不可变的。
再比如,一个单独的String类型,我们认为它是不可变的;但是如果将其放入一个ADT中,并提供相应的set()方法,那么它便使该ADT变成了mutable的。
说到这里,可能就有人会糊里糊涂了,那么到底什么是可变,什么是不可变呢?
其实也很容易理解,就像是ADT之间的等价性分为行为等价性和观察等价性两种一样,我认为ADT的可变与不可变也可分为行为与观察两种。
- 从行为角度看,一个ADT如果有能够使其改变的方法(不管有没有暴露出来),那么它就是mutable的;
- 而从观察者角度看来,我们并不知道ADT的内部构造,只能够看到其暴露出来的方法,如果其中有mutator,那么该ADT是mutable的。
在实际编程过程中,我们往往不知道ADT的底层实现细节,只能够通过暴露出来的方法,来使用ADT,故我们认为的可变与不可变大多是站在观察者角度的。
如果你看懂了以上内容,那么考虑下一个问题:不可变的的ADT中成员变量一定是使用final修饰的嘛?
很显然,我认为不一定必须要用final修饰(虽然实际编程中经常使用final修饰)。我们通常会使用final来修饰,我认为是因为我们无法保证ADT中绝对不会出现对该成员变量的mutator方法,而并不是因为该ADT的不可变性。
如果我们没有暴露出该成员变量的mutator方法,那么其实用不用final修饰,好像都是一样的,起码在Client端使用该ADT的时候,Client不会认为这是一个mutable数据类型。
综上所述,我认为可变与不可变的划分并没有一个绝对的界限,关键在于你站在什么角度来看待的这个问题。
二.我们什么使用可变数据,什么时候使用不可变数据?
正如ADT的可变与不可变不是绝对的一样,二者在不同角度上也有不同的优劣性。
- 站在性能的角度上看,那必然是可变数据类型要优越的多。
在生活中,我们身边其实也有很多可变与不可变的例子。例如,我们在使用word时对文章的修改,便是一个可变的例子。即使在文档保存了之后,我们仍然可以对其再次修改,这就是一种可变性;如果文档保存后不可变,那么当我们想要对其修改的时候,就需要重新拷贝一份文件,这不仅浪费了我们的时间,也消耗了硬盘的存储空间。在编程过程中也同样如此,使用不可变数据类型往往存在大量的拷贝,在时间和空间上都不如可变数据类型。
因此,性能上,可变数据类型更胜一筹。
- 站在安全的角度上看,不可变数据类型又显得可靠得多。
人一出生,性别便是不可变的(呃,变性暂不考虑),如果性别可以随意改变,那必然会引起很多不安全的现象,就像男厕,女厕到底该进哪一个,就很难选择。所以对此种情况,我们往往采用不可变数据类型,保证其不可变性。编程中,那些我们不希望别人可以随便更改的内容,也往往使用不可变的数据类型要安全的多。
还有就是在多线程的编程中,或是多人协作,分布式部署等。我们也往往更喜欢使用不可变数据类型,因为有些数据是共享的,我们无法确保其他线程/人/服务器不对共享数据进行我们不希望发生的更改。
因此,不可变数据类型虽然性能不占优,但是在有些时候,更能使我们安心。
总结
本文主要讨论了抽象数据类型(ADT)的可变与不可变性的区分,以及二者的适用情况,希望能对读者理解ADT的可变与不可变有所帮助。