文章目录
3.1 数据类型与类型检验
基本数据类型 | 对象数据类型 |
---|---|
存在栈里 | 存在堆里 |
只有值,没有ID(与其他值无法区分) | 既有ID,也有值 |
Immutable | mutable |
2. 静态/动态类型检测
考试可能重点:1. 画图 2. 转化为immutable
静态类型检测:赋的值是否在变量类型范围内
动态类型检测:运行的时候针对某个值检测(注:JAVA是静态类型检测,运行时不会做类型检测)
动态>>静态>>无检查
静态类型检测 | 动态类型检测 |
---|---|
编译阶段检查 | 运行阶段检查 |
- 静态类型检测
语法错误
类名/函数名错误
参数数目错误
参数类型错误
返回值类型错误 - 动态类型检测
非法的参数值
非法的返回值
越界
空指针
3. mutable和immutable
- immutable
改变值的时候要申请一个新的存储空间,然后使变量指向新的存储空间
注:
1) 不包含任何可以改变属性值的方法(eg:setter)
2)属性中无public
3) 不能直接return属性值,而是要return一个copy
2. mutable:值可以被改变
危险(多个引用共享数据),但同时可以提高效率,不用产生大量需要回收的无用变量
当返回groundhogAnswer时返回的相当于是它的引用,相当于groundhogAnswer和partyDate现在指向的是同一个内存区域
3. final
1. final类无法派生子类
2. final变量无法改变值/引用
3. final方法不能被子类重写
3.4 把mutable转化成immutable
- 防御式拷贝defensive copy
例如:把return groundhogAnswer;改为return new Date(groundhogAnswer.getTime());
缺点是:可能造成大量的内存浪费
改为:
从而在下图应用中不会因为end.setYear(78)改变p的值
- snapshot diagram
- 基本类型值/对象类型值
基本类型不会把引用存在堆里(不会存变量名)
对象的属性(如x,y)和对象一起放在堆里
对象的引用放在堆里 - immutable/mutable/final
immutable对象:双线椭圆
mutable:
final:不可变的引用(双线)
注:引用不可变,但指向的值可以变(是mutable时可以改变值)
- 基本类型值/对象类型值
4. 常见数据集合类型
- Array
a[2]
a[2]=0
a.length - List
List中的每个成员都是对象
list.get(2)
list.set(2,0)
list.size() - Set
没有重复元素
s1.contains(e)
s1.containsAll(s2)
s1.removeAll(s2) - Map
map.put(key,val)
map.get(key)
map.containsKey(key)
map.remove(key)
4.5 迭代Iterator
- 一个iterator的类的内部实现
public class MyIterator {
private final ArrayList<String> list;
private int index;
/**
* Make an iterator
* @param list list to iterate over
*/
public MyIterator(ArrayList<String> list) {
this.list = list;
this.index = 0;
}
/**
* Test whether the iterator has more elements to return
* @return true if next() will return another element
* false if all elements have been returned
*/
public boolean hasNext(){
return index < list.size();
}
/**
* Get the next element of the list
* Requires: hasNext() returns true
* modifies:
* @return next element of the list
*/
public String next(){
final String element = list.get(index);
++index; //改变了index的值,故该类是mutable的
return element;
}
}
画出来的snapshot图:
2. remove时用iterator的remove取代list的remove
改为:
5. useful immutable types
mutable: Date,List,Set,Map
immutable: 通过以下方法使mutable变为不可变(编译阶段无法静态检查)
Collections.unmodifiableList
Collections.unmodifiableSet
Collections.unmodifiableMap
3.2 设计规约
- 参数类型、返回值类型是否匹配,在静态类型检查阶段完成
- 规约的作用:
- 规约可以隔离“变化”
- 规约可以提高代码效率,实现者不用写代码保证输入的正确性
- 规约扮演“防火墙”的角色
- 规约内容
- 输入/输出的数据类型
- 功能和正确性
- 性能
- 性质
行为等价性—— 站在客户端视角 - 规约结构
- 前置条件 requires——客户端约束(java:@param)
- 后置条件 effects——开发者约束(java:@return)
- 异常处理 前置条件违反的情况(java:@throws)
注:没写异常处理时,只要违反了前置条件,那么得到任何结果都是正确的
- 依据规约的黑盒测试
根据规约设计测试用例,不考虑实现,同其他client一样
不能在test时猜测实现find的内部方法
7. 规约的比较
- 规约的确定性
确定的规约:给定precondition输入,输出是唯一的、明确的 - 规约的陈述性
操作式规约:eg:伪代码
*声明式规约:没有内部实现的描述,只有“始、终”
7.3 规约的强度Strong(**)
规约强度S2>=S1:S2前置条件更弱,或(保证S1的前置条件的基础上)后置条件更强
此时可以用S2替换S1
无法比较的情况:越强的规约,更少的实现可以满足,更多的client可以调用
- 规约图
- 椭圆代表规约,点代表实现
- 更强的规约,表达为更小的区域
9. 分析规约
- 内聚的
Spec描述的功能应该单一、简单、容易理解
最好不要把两个功能放在一个方法实现
反例: - 信息丰富的
return不要出现二义性 - 规约应该足够强壮Strong
开发者应该考虑各种特殊情况并在post-condition给出处理措施 - 规约也不能太强
太强的spec很多时候难以达到,给开发者增大难度
反例: - 规约里使用抽象类型
例如尽量使用List、Set而非ArrayList、HashSet,会给方法实现和客户端更大的自由度 - 前置条件还是后置条件?
1. 不写precondition代价很大时,在规约里加入precondition可以把责任给client
2. 方法使用范围只在类的内部时,可以不用precondition
惯用做法:precondition不太强,而是在postcondition中抛出异常:输入不合法
3.3 抽象数据类型(ADT)***
- ADT的特性:表示泄露、抽象函数AF。表示不变量RI
2. 方法的几种类型
- Creators 构造器
可能实现为构造函数/静态函数。
当需要使客户端能调用构造器,又不想让调用者看到creators时可以使用工厂方法 - Producers 生产器
- Observers 观察器
- Mutators 变值器——直接改变对象属性的值(只存在于mutable类中)通常返回void,但也可以返回值非空
3. 常见分类
4. 设计抽象数据型
1. 操作简洁、一致
2. 操作要足够多
getter、setter、或提供size操作就不用每次都遍历得出list的size了
5. 表示独立性
client使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不影响外部Spec和客户端**(把所有属性设为private)**
不直接访问属性,通过get方法访问
6. 测试ADT
测试creators、producers、mutators时:调用observers来观察这些方法的结果是否满足规约
测试observers:调用creators、producers、mutators产生/改变对象,看结果是否正确
- 不变性
例如当使用Date类型(mutable)时为了保证不变性,使用提供的对应immutable类java.time.ZonedDateTimereturn属性值时表示暴露
且HYPOTENUSE没用private修饰
8. AF与RI
- RI:可以映射到用户看到数据集合的条件
- AF:用户看到的数据
- checkRep()
8.4 safety from exposure
反例:
(Set和Map都是mutable类型的)
可以两种方式修改以防止表示暴露:
1. getFollowers时返回defensive copy
2. getFollowers时返回immutable类型的Set