目录
(1)Snapshot diagram中的基本类型与对象类型的值
4-5章考点如下:
一、数据类型与类型检验
1. 基本数据类型、对象数据类型(4.1)
数据类型(Types):一组值,以及可以对这些值执行的操作。示例:boolean、int、double、String
变量(Variables):用特定数据类型定义,可存储满足类型约束的值
基本数据类型(primitive types):int,long,boolean,double,char
对象数据类型(object types ):String,BigInteger
基本类型是小写的,而对象类型是以大写字母开头的。
2. 静态类型检查、动态类型检查(4.2)
静态类型语言:在编译阶段进行类型检查,如Java
动态类型语言:在运行阶段进行类型检查,如Python
三种自动检查:
1. 静态类型检查:在程序运行之前自动发现错误。
2. 动态类型检查:执行代码时自动发现错误。
3. 无检查:语言根本不能帮助您找到错误。会得到错误的答案。
静态类型检查 >> 动态 >> 无检查
(1)静态类型检查
静态类型检查:可在编译阶段发现错误,避免了将错误带入到运行阶段,可提高程序正确性/健壮性。
包括:
1. 语法错误(Syntax errors): 比如额外的标点符号或者虚假的单词。甚至像Python这样的动态类型语言也会进行这种静态检查。
2. 类名/函数名错误(Wrong names): 例如 Math.sine(2) . (正确为 sin)
3. 参数数目错误(Wrong number of arguments): 例如 Math.sin(30, 20) .
4. 参数类型错误(Wrong argument types):例如 Math.sin("30") .
5. 返回值类型错误(Wrong return types): 例如一个函数声明返回int ,却 return "30"; .
(2)动态类型检查
包括:
1. 非法的参数值(Illegal argument values):例如,整数表达式x/y只有在y实际为零时才是错误的;否则就行了。所以在这个表达式中,被零除不是静态误差,而是动态误差。
2. 非法的返回值(Unrepresentable return values): 例如,当特定的返回值不能在类型中表示时。要求boolean,返回int。
3. 越界(Out-of-range indexes): 例如,在字符串上使用负数或太大的索引。
4. 对空对象引用调用方法。如空指针。
(3)静态与动态检查比较
静态检查往往是关于类型,即与变量的特定值无关的错误。静态类型保证变量将具有该集合中的某个值,但直到运行时我们才确切知道它具有哪个值。因此,如果错误仅由某些值引起,如被零除或索引超出范围,则编译器不会对此引发静态错误。
相比之下,动态检查往往是由特定值引起的错误。
3. Mutable/Immutable(4.3)
(1)Immutable
不变数据类型(Immutable types):一旦被创建,其值不能改变
Java还为我们提供了不可变的引用:只分配一次而从不重新分配的变量。如果是引用类型,也可以是不变的:一旦确定其指向的对象,不能再被改变要使引用不可变,使用关键字final声明它
final int n = 5;
final Person a = new Person(“Ross”)
如果编译器无法确定final变量不会改变,就提示错误,这也是静态类型检查的一部分。
所以,尽量使用final变量作为方法的输入参数、作为局部变量。
final性质:
1. final类无法派生子类 2. final变量无法改变值/引用 3. final方法无法被子类重写
(2)Mutable
不变对象:一旦被创建,始终指向同一个值/引用,例如String
可变对象:拥有方法可以修改自己的值/引用,例如StringBuilder
在多个引用对象时出现差异
1. 可变数据类型的优势
使用不可变类型,对其频繁修改会产生大量的临时拷贝(需要垃圾回收)。可变类型最 少化拷贝以提高效率。
使用可变数据类型,可获得更好的性能。也适合于在多个模块之间共享数据。
2. 可变数据类型的劣势
不可变类型更“安全”, 在其他质量指标上表现更好。
可变数据类型容易使函数超出了spec范畴,因为它改变了输入参数的。这种错误非常难于跟踪和发现。对其他程序员来说,也难以理解。
4. 值的改变、引用的改变(4.3)
赋值:使用“=”为变量赋值。
String foo;
foo = “IAP 6.092”;
赋值可以与变量声明结合使用。
double badPi = 3.14;
boolean isJanuary = true;
改变一个变量:将该变量指向另一个值的存储空间。
改变一个变量的值:将该变量当前指向的值的存储空间中写入一个新的值。
5. 防御式拷贝(4.3)
防御式拷贝:返回对象的新副本,例如给客户端返回一个全新的Date对象
不可变类型永远不需要防御式拷贝。
大部分时候该拷贝不会被客户端修改, 可能造成大量的内存浪费。如果使用不可变类型, 则节省了频繁复制的代价。
安全的使用可变类型:局部变量,不会涉及共享;只有一个引用。如果有多个引用(别名),使用可变类型就非常不安全。
6. Snapshot diagram(4.4)
Snapshot diagram:用于描述程序运行时的内部状态
用处:1. 便于程序员之间的交流 2. 便于刻画各类变量随时间变化 3. 便于解释设计思路
(1)Snapshot diagram中的基本类型与对象类型的值
基本类型的值:
–基本类型的值由裸常量表示。传入箭头是对变量或对象字段中的值的引用。
对象类型的值:
–对象值是按其类型标记的圆。
–当我们想显示更多细节时,我们会在里面写上字段名,箭头指向它们的值。更详细地说,这些字段可以包括它们声明的类型。
(2)重新赋值和不可变值
例如,如果我们有一个字符串变量s,我们可以将其从值“a”重新赋值为“ab”
String s=“a”;
s=s+“b”;
String是不可变类型的一个例子,这种类型的值一旦被创建就永远不会改变。不可变的对象(由它们的设计器设计为始终表示相同的值)在Snapshot diagram中用双边框表示,就像我们的图中的字符串对象一样。
不可变对象:用双线椭圆
(3)可变值
可变值
StringBuilder(一个内置的Java类)是一个表示字符串的可变对象,它具有更改对象值的方法:
StringBuilder sb = new StringBuilder("a");
sb.append("b");
(4)不可变引用
不可变的引用: 只分配一次而从不重新分配的变量。
使引用不可变:使用关键字final声明它:
在Snapshot diagram中,不可变的引用(final)由双线箭头表示。
注:1. 引用是不可变的,但指向的值却可以是可变的,例如:final StringBuilder sb
2. 可变的引用,也可指向不可变的值,例如:String s
二、设计规约
1. Specification、前置/后置条件
(1)Specification(5.2.2)
规约(Specification、Contract):程序与客户端之间达成的一致
Spec给“供需双方”都确定了责任,在调用的时候双方都要遵守
功能:
1. 规约可以隔离“变化”,无需通知客户端
2. 规约也可以提高代码效率
3. 扮演“防火墙”角色,不需了解具体实现
对象与其用户之间的统一:1. 输入/输出的数据类型 2. 功能和正确性 3. 性能
只讲“能做什么”,不讲 “怎么实现”。
(2)前置/后置条件(5.2.4)
方法规约包含以下条目:
1. 前置条件(Precondition):对客户端的约束,在使用方法时必须满足的条件(requires)
2. 后置条件(Postcondition):对开发者的约束,方法结束时必须满足的条件(effects)
3. 异常行为
前置条件满足,则后置条件必须满足。
前置条件不满足,则方法可做任何事情。
静态类型声明是一种规约,可据此进行静态类型检查static checking。
前置条件:@param
后置条件:@return 和 @throws
规约不应该涉及方法的局部变量或方法类的私有字段。
除非在后置条件里声明过,否则方法内部不应该改变输入参数。应尽量遵循此规则,尽量不设计 mutating的spec,否则就容易引发bugs。
2. 行为等价性(5.2.3)
行为等价性:是否可相互替换
需要站在客户端视角看行为等价性。
根据规约,判断是否行为等价
例:对从前向后查找与从后向前查找而言,当要求有只出现一次时,便会等价。
3. 规约的强度
规约的强度 S2>=S1:1. S2前置条件更弱 2. S2后置条件更强
就可以用S2替代S1
条件强弱从集合上考虑是:弱的集合包含强的集合
例:出现多次包含出现一次,寻找一个包含寻找第一个
注:1. nothing是最弱的条件
2. 后置条件的调整需要在原来前置条件的基础上考虑
3. 当规范得到加强时:满足它的实现更少,更多客户可以使用它