第四章 数据类型与类型检验
JAVA中数据类型分为
基本数据类型
int long boolean double char......
对象数据类型
String BigInteger......
按照 Java 约定,基本类型是小写的,而对象类型以大写字母开头。
而其中对象数据类型会形成层次结构
如果 extends 子句省略,默认为 Object。所以object是所有对象数据类型的父类
基本类型可以包装为对象类型
通常是在定义容器类型的时候使用它们(容器类型操作的元素要求是对象类型,所以需要对基本数据类型进行包装,转换为对象类型)
如果编写容器操作基本数据类型,一般会自动转换
静态类型语言 动态类型语言
静态类型语言所有变量的类型在编译时已知,因此编译器可以推导表达式类型
动态类型语言在运行阶段进行类型检查
注意和静态动态检查区分,无论是静态类型语言还是动态类型语言都既会进行静态检查又会进行动态检查
可变数据类型和不可变数据类型
不变数据类型:一旦被创建,其值不能改变
final int n = 5;
如果是引用类型,也可以是不变的:一旦确定其指向的对象,不能再被改变指向其他对象
final Person a = new Person(“Ross”); 可以更改类里面的数据,但是a的指向不能更改
final类无法派生子类
final变量无法改变值/引用
final方法无法被子类重写
可变数据类型和不可变数据类型要和不变数据类型区分
在快照图中final相当于给箭头加双线,与椭圆无关
而可变与不可变依据椭圆是否双线划分
可变类型因为最少化拷贝,可以提高效率
使用可变数据类型,可获得更好的性能也适合于在多个模块之间共享数据
不可变类型,对其频繁修改会产生大量的临时拷贝(需要垃圾回收)
不可变类型更“安全”,在其他质量指标上表现更好
折中使用
Snapshot diagram
用于描述程序运行时的内部状态,便于程序员之间的交流,便于刻画各类变量随时间变化,便于解释设计思路
基本类型的值
对象类型的值
不可变对象:用双线椭圆
不可变的引用:用双线箭头
数组和容器
List 顺序表
Set 集合
Map 图(类似于字典
同时容器提供对应的操作方法,不再赘述
迭代器 迭代器是一个对象,它遍历一组元素并逐个返回元素。
形如
的遍历就是通过迭代器实现的,String表示city的类型,:cities表示city是来自容器cities的
第五章 设计规约 Spec
规约及其优点
规约可以理解为对方法的一个描述,约束了调用者的输入和实现者需要完成的功能
规约一般包括
requires前置条件:对客户端的约束,在使用方法时必须满足的条件
effects后置条件:对开发者的约束,方法结束时必须满足的条件
契约:如果前置条件满足了,后置条件必须满足
异常行为:如果违反前提条件,它会做什么
优点
1.规约可以隔离“变化”,无需通知客户端
2.规约也可以提高编码效率(E.g., 实现者不需要写代码确保输入的正确性,调用者的责任)
3.规约:扮演“防火墙”角色:客户端不需要知道实现,实现者不需要知道如何被使用
根据规约可以判断是否行为等价,既是否可以相互替换
方法的规约可以谈论方法的参数和返回值,但绝不能谈论方法的局部变量或方法类的私有字段。
一般规定除非在后置条件里声明过,否则方法内部不应该改变输入参数
应尽量遵循此规则,尽量不设计mutating的spec,否则就容易引发bugs
规约的设计
规约的确定性
规约的陈述性
规约的强度 用于判断“哪个规约更好” 规约强度S2>=S1,S2可以替换S1
规约的替换是针对同一个方法的规约,是为了更准确的描述方法,选择更合适的规约
规约强度的判断
规约S2和S1,若S2同时满足
1.前置条件更弱或相等(不强于)
2.后置条件更强或相等(不弱于)
则规约的强度S2>=S1,就可以用S2替代S1
规约的图表示
方法的某个具体实现,若满足规约,则落在其范围内;否则,在其之外。
更强的规约,表达为更小的区域
好的Spec描述的功能应单一、简单、易理解
选择强度合适的spec
是否使用前置条件取决于(1) check的代价;(2) 方法的使用范围
使用前置条件就是指不对输入做额外的检查(不需要判断输入是否满足)
第六章 抽象数据类型 (ADT)
抽象数据类型就是将数据和可以对其进行的操作结合起来
例如数学,数字就是这个抽象数据类型的数据,而加减乘除等就是它的操作
对抽象类型的操作进行分类
数据的类型已经在前文讨论过了
操作分为四类 (用int举例
1.构造器(从无到有) (0 , 1 , 2 , ...
2.生产器(从有到新) (+ , - , * , /
3.观察器 (== , != , < , >
4.变值器,改变对象属性的方法 int不可变,所以没有
如果一个抽象数据类型没有变值器,那么他是不可变的immutable
设计的抽象类型应该
1 设计简洁、一致的操作 功能应单一
2 要足以支持client对数据所做的所有操作需要,且用操作满足client需要的难度要低 用户友好
3 要么抽象、要么具体,不要混合 --- 要么针对抽象设计,要么针对具体应用的设计
比如Set就是一个通用的容器,而Person是针对特定的要求设计的
表示独立性
client使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不应影响外部spec和客户端
简而言之就是,实现者可能有多种方法实现需求,但用户不必得知,例如程序功能是排序,实现者可以选择冒泡排序,桶排序等等
测试creators, producers, and mutators:调用observers来观察这些operations的结果是否满足spec;
测试observers:调用creators, producers, and mutators等方法产生或改变对象,来看结果是否正确
不变性
ADT需要始终保持其不变量,就例如一个Person类,如果把它其中的 String name这个变量设为public类型的,那么我们本来没有提供修改name这个操作,但是用户可以自行修改
就好像,小明 攻击力100用户修改name为小红,这就与我们本来的目的背道而驰
而上述所说的导致的行为叫做表示泄露
这种设计不仅影响不变量,也影响了表示独立性:无法在不影响客户端的情况下改变其内部表示
代表不变量和抽象函数
表示空间 表示值构成的空间:实现者看到和使用的值
抽象空间 抽象值构成的空间:client看到和使用的值
ADT开发者关注表示空间R,client关注抽象空间A
R到A应该是满射、非单射、未必双射,也就是说A中的每个值,R中至少一个对应
抽象函数 AF : R → A
抽象函数:R和A之间映射关系的函数,即如何将R中的每一个值解释为A中的每一个值
表示不变性 RI : R → boolean
表示不变性RI:某个具体的“表示”是否是“合法的”
也可将RI看作:所有表示值的一个子集,包含了所有合法的表示值
也可将RI看作:一个条件,描述了什么是“合法”的表示值
总之:
选择某种特定的表示方式R,进而指定某个子集是“合法”的(RI),并为该子集中的每个值做出“解释”(AF)——即如何映射到抽象空间中的值
设计ADT:(1) 选择R和A;(2) RI --- 合法的表示值;(3) 如何解释合法的表示值 ---映射AF
确保ADT安全
1.构造器和生产器在创建对象时要确保不变量为true
2.变值器和观察器在执行时必须保持不变性
3.在每个方法return之前,用checkRep()检查不变量是否得以保持