软件构造心得(四)
本文主要讲述了不可变数据类型(immutable)与可变数据类型(mutable)间的区别,并简述设计规约的编写作用与规则(在后面ADT中会进一步具体化)。
数据类型
在对于软件构造(Java)的学习中,最为基础且重要的就是要学习其数据类型及其存储方式。就在我认为其数据类型基本与c语言有异曲同工之妙时,才发觉其中有许多不同。发生不同的最主要原因是Java是一门面向对象的语言,因此Java中不仅有基础数据类型,还有对象数据类型。
但在介绍对象数据类型之前,还需要介绍一个非常重要概念,就是数据类型的可变性与不可变性(即mutable和immutable)。它们的区别在于不可变类型不能够改变对象中的值/引用,而可变数据类型则可以。在这里我们要理解Java中变量是如何表示的,如果按c语言中的想法那不可变类型是不是就是const/static了呢?显然不是的,因为这里说的不可变是具体值的不可变。Java中对于每一个变量,都会指向一个具体的对象,每一次使用这个变量,它表示(调用)的就是这个变量所指向的具体对象,就有点像变量是一个指针,每次都会调用它所指向的地址的值。而不可变指的就是具体对象其不可被修改,而不是字面上的变量不可变。而在不可变类型下,修改变量的值就不是直接对其指向的具体对象(地址内容)进行修改,而是复制一个新的副本(新对象),在新的对象上进行修改。
下面举一个不可变与可变数据类型差异的例子(其主要体现在修改上):
//在Java中,String是不可变类型,而StringBuilder是可变数据类型 //Exemple1: String str1=new String(”A“),str2; str2=str1; str1+="B"; //Exemple2: StringBuilder str1=new StringBuilder(”A“),str2; str2=str1; str1.append("B");
在例一中,str1是“AB”,而str2是”A“,因为在修改的过程中,str1先是复制了一个副本(内容为”A“),然后在此副本上修改为“AB”,而str2还是指向了原来的“原件”,所以还是"A"。在例二中,str1和str2都是”AB“,因为str1和str2指向的都是同一个对象(地址相同),因此可变类型是直接对该地址进行修改,所以两个变量值都改变。
这里可以回顾心得(一)中单元测试中的assertSame和assertEquals的区别,如果以上两个例子中往字符串后面加的是空串,例一用assertSame它会抛出异常,而例二不会;因为虽然str1和str2的值是相同的,它们指向的对象(地址)却不同,一个指向原件一个指向副本。因此,这也是为什么心得(一)中会说“==”号在对象数据类型中就是判断其地址是否相同,因为同一地址代表同一对象,不同地址就算值相同,其表示的也不是同一对象(就像克隆人和其真人不是同一个个体一样)。这也是为什么开头说与c语言的区别主要源于对象数据类型的诞生。
下面我们简单介绍一下Java中同时存在的基本数据类型和对象数据类型。为了要同时能够出现在Java中,用来表示、比较它们的规则一定要统一(即底层实现上对象数据类型与基本数据类型的逻辑相同)。在Java中,基本数据类型是不可变的,是否相同取决于值大小是否相同(与c语言中一致),但是在对象数据类型中是否相同取决于地址是否一致,这两种看似矛盾的性质是怎么统一起来的呢?(如果某一变量类型既是基本数据类型又是对象数据类型那岂不是乱了套?)
这就要再次回到心得(一)中”==“和equals的辨析中浅浅提到过的直接创建和对象创建。顾名思义,直接创建就是作为基本数据类型而创建,而对象创建就是作为对象数据类型创建。Java中将直接创建放入公共池中,公共池的特性就是相同的值只会出现一次(集合),所有相同的值的变量都会指向同一个地址。那么在基础数据类型中,就可以保证值相等的变量一定指向同一个对象,在”==“的判断中就能很好地与对象数据类型相统一起来(因为它就是把基础数据类型视为了一类特殊的对象)。
可变类型和不可变类型都有其使用的地方。可变类型效率高(无需复制),且在不同类间可以传递数据。而不可变类型应用更为广泛,它可以使得其他变量/类中的修改对其本身的值没有影响,极大提高了安全性(若用可变数据类型,则可能在一些方法中作为参数会其值被修改,如c语言中传入形参/引用的区别)。
注:Snapshot Diagram中,不可变的都用双线表示,不可变对象用双线圆、不可变引用用双线箭头。