OO第三单元总结
第三单元作业难度较前两单元有所降低,难点在于Java Modeling Language的阅读理解和使用高效算法提高性能。
实现规格要求的设计策略
1.浏览全部接口JML规格描述,大致读懂整个程序需要实现的功能:社交网络的模拟
2.按照点-->网络的顺序逐个实现,如在第一次中先编写MyPerson、MyMessage(节点类),再实现MyNetwork(网络类)中各方法,当遇到有可能抛出异常时,实现异常接口的类。 只有充分理解节点类的所有属性和可使用公用方法才能很好地实现网络类。
3.在每个类实现前选择合适的属性的容器(在容器的选择和使用重点描述)
4.在实现MyNetwork时选择高效的图算法
(1)isCircle使用并查集算法,将复杂度控制在O(1);
(2)queryBlockSum直接统计并查集选出的“块首领”的个数,复杂度为O(1);
(3)sendIndirectMessage使用优化的Dijkstra查找最短路径。
基于JML规格测试的方法和策略
JML阅读语法:
(1)\result:表示方法的返回值。
(2)\requires:类似if,表示执行的条件。
(3)\old(expr):表示expr在方法执行前的值。
(4)\forall:全称量词修饰的布尔表达式,可声明局部变量、覆盖变量的取值范围,对目标条件进行验证。
(5)\exists:存在量词修饰的布尔表达式,亦可声明局部变量、覆盖变量的取值范围,对目标条件进行验证。
(6)\sum, \max, \min:对给定范围的表达式进行运算。
(7)==>:逻辑推理表达式,类似于if。
(8)assignable \everything:通过该方法需要更改的属性范围。
通过从exceptional_behavior到normal_behavior的顺序阅读每个方法需要实现的功能,理解每个语句的具体要求,将其和转化成的对应语句块进行对比,检查测试。
JMLUnitNG的自动化生成测试样例可以测试的范围很有限,评测效果并不好。
debug最有效的方法仍然是自动化生成大量测试样例,通过对拍寻找。
容器的选择和使用
1.本单元第一次作业中,按照规格的for循环书写,最自然地选择了ArrayList容器存放MyPerson中的acquaintance、value、messages属性,MyNetwork中的people属性,但在第一次互测时通过阅读其他人的代码,开始意识到HashMap非常契合本单元的作业要求(因为几乎每个对象:从人到各类消息、组都有自己的唯一编号),所以之后添加的属性组基本都使用了HashMap容器。
2.本单元作业中最多使用的容器是HashMap。
优点:HashMap是一个散列表,它存储的内容是键值对(key-value)映射,因为实现了 Map 接口,根据键的 HashCode 值存储数据,所以具有很快的访问速度。
本单元主要使用的HashMap方法有:
| 方法 | 描述 |
|---|---|
isEmpty() |
判断 hashMap 是否为空 |
size() |
计算 hashMap 中键/值对的数量 |
put() |
将键/值对添加到 hashMap 中 |
remove() |
删除 hashMap 中指定键 key 的映射关系 |
containsKey() |
检查 hashMap 中是否存在指定的 key 对应的映射关系。 |
containsValue() |
检查 hashMap 中是否存在指定的 value 对应的映射关系。 |
get() |
获取指定 key 对应对 value |
3.还有就是优化Dijkstra时使用的PriorityQueue优先队列.,其本质小顶堆,poll()方法返回队列中最小值,复杂度为O(1)。
性能问题
1.isCircle()判断联通,使用并查集算法——增加HashMap<Integer, Integer> link记录每个节点(人)所在块的首领,addPerson时link.put(person.getId(), person.getId());,addRelation时判断两人是否处在同一个块,如果不是,则合并首领(即合并两块),最后isCircle()时只需要判断是否两人为同一首领,此方法将复杂度控制在O(1);
2.queryBlockSum()计算连通块总数,只需要遍历一遍节点(人),查找到所有首领是自己的人,数量即为连通块总数。
3.getAgeVar(),通过每次组中addPerson()时增加总年龄totalAge(),计算年龄方差时只需要调用复杂度为O(1)的getAgeMean()方法(return totalAge / getSize();):
for (Person person : people.values()) {
sum += (person.getAge() - getAgeMean()) * (person.getAge() - getAgeMean());
}
return sum / getSize();
复杂度为O(n)。
4.sendIndirectMessage()中需要找到最短路径,使用PriorityQueue优先队列优化Dijkstra,其本质小顶堆,poll()方法返回队列中最小值,复杂度为O(1)。
作业架构设计

本单元作业架构不需要多加设计,只需要将每个自己的类实现一个提供的接口:MyPerson实现Person接口,MyNetwork实现Network接口,MyMessage实现Message接口,MyGroup实现Group接口,MyEmojiMessage实现EmojiMessage接口...最重要的还是要找到类之间、类内部方法之间的关系,在实现一个具体方法时可以在JML要求之外,添加为了实现其他功能而做的操作。
心得体会
1.JML比自然语言理解要费时费力,但表述精确无二义性,阅读时需要认真仔细
2.通过练习,巩固了图论相关的几个经典算法:从并查集到优化的Dijkstra
3.在使用中熟悉了HashMap和PriorityQueue等容器的适用场景,并提高了效率

浙公网安备 33010602011771号