※本章为重点内容
ADT和OOP【Ⅰ】
1 数据类型与类型检验
1.1 数据类型
-
数据类型可以分为
- 基本数据类型:int、long、boolean、double、char等等
- 面向对象的数据类型:String、BigInteger等
-
变量variables:
某一数据类型下具体定义的指代,比如String s -
两种数据类型的比较
基本 | 面向对象 |
---|---|
只有值,没有ID | 有值和ID |
immutable | mutable |
在栈中分配内存 | 在堆中分配内存 |
代价低 | 代价高 |
比如int a=3,java在栈中只存3
-
对象类型形成层次结构——继承
- 如果“B extends A”,则任意B都是A
- 此知识点在之后会讲到
1.2 静、动态数据类型检测
1.2.1 概述
-
static:
- 检测所赋值是否与变量类型匹配
- 针对的是 类型
- 在 编译阶段 发现error
- 比如 int a=“abc”
-
dynamic
- 检测某一个具体值在当前环境下是否合法
- 针对的是 具体值
- 在 运行阶段 检测
- 比如:
int a,b,c;
c=a/b;//此时b可能==0
- 动态检查>静态检查>无检查
1.2.2 静态检查
- 语法错误
- 类/函数名
- 参数数目
- 参数类型
- 返回值类型
1.2.3 动态检查
- 非法参数值
- 非法返回值
- 越界
- 空指针
1.2.4 例题
{
int n=5;
if(n)...//static error参数类型错误
}
{
double a=1/5;//no error, wrong answer
}
{
int sum=0,n=0;
int a=sum/n;//dynamic error除数为0
}
{
double sum=7,n=0;
double a=sum/n;//infinity无限
}
1.3 ※Mutability&Immutability
1.3.1 如何改变某个变量的对应值?
-
直接改变值
比如
String s=new String();
s=“123”;
s=“456”; -
指向另一个变量
比如
String s1=new String();
String s2=new String();
s1=“123”;s2=“1234”;
s1=s2;
1.3.2 Immutability
不可改变储存空间内的值,只能改变所指向的变量
-
类比final:
- final控制的是 变量的指向不变
比如final String a;那么就不能改为指向b - final class: 无法派生子类
final变量: 无法改变引用
final方法: 无法被子类重写
- final控制的是 变量的指向不变
1.3.3 mutable和immutable的差异
- String为immutable
而StringBuilder为immutable
String s="a";s=s.concat("b");
String t=s;t=t+"c";
StringBuilder sb=new StringBuilder("a");
sb.append("b");
StringBuilder tb=sb;
tb.append("c");
- String中s.concat(),s.trim()等均返回一个copy,而不对s本身进行指向和内容的修改
也就是说,如果要改变s,需要
s=s.concat(“a”);
1.3.4 常用数据类型的可变性
- String——immutable
- StringBuilder——mutable
- List——mutable
- Date——mutable
- LocalDateTime——mutable
- 数组——mutable
1.3.5 mutable下的防御式拷贝
返回拷贝后的一个新的对象,避免原有对象空间的暴露。
比如不要return a ;而是return new Date(a.getTime());
从而使一个引用指向单独的空间对象,避免多个引用指向一个对象。
1.3.6 如何判断一个类为immutable
- 类中没有任何能改变属性值的方法
- 属性不可为public——避免外部访问
- 方法不可返回属性的引用——避免属性暴露
- constructor中赋值需要进行defensive copy(如果是mutable的话)——避免外部属性深入
public class immutable{
private final ...a;
public immutabletype f1(){
...
return a;//if a immutable ,then this is okay.
}
public mutabletype f2(){
...
return new mutabletype...;//if a mutable
}
//constructor
this.a=new mutabletype ...;//if a is a mutable
}
1.4 Snapshot diagram
- code-level; run-time;moment
- 描述运行时的内部状态
画法:
-
基本values
-
对象values
- 对象的属性:比如例子中的int x,y也存在heap中
- 引用存在stack中,比如p
但是引用的内容(圈里的东西)存在heap中
-
immutable类型用双圈表示
-
final不可变引用用双线箭头表示
上述例子中的id被final修饰了
-
显然,若final+immutable,那么这个变量就被彻底固定了
- 如果尝试改变final修饰的引用的指向,会在编译阶段报错
String s1=new String("abc");
List<String> list=new ArrayList<>();
list.add(s1);
s1=s1.concat("d");
String s2=s1.concat("e");
list.set(0,s2);
删除功能的iterator
Iterator iter=subjects.iterator();
while(iter.hasNext()){
Subject subject=iter.next();
if(subject满足的条件){
iter.remove();
}
}
- StringBuilder 的toString功能会 生成一个新的String对象。如果需要调用这个对象,需要设置新的引用,原StringBuilder的引用不变
2 规约spec
- 测试用例不可违背规约
- spec包括 方法名+注释
2.1 强度
2.1.1 s2强于s1
s2的实现一定可以实现s1的内容
- 前置precondition约束更弱(对输入的要求弱)
- 后置postcondition约束更强(对输出的要求强)
当s2强于s1,可以用s2代替s1
2.1.2 无法比较的情况
- s1的pre-强于s2,但是s1的后置也强于s2
- s1和s2的post-侧重不同
满足上述任一条件,都无法比较强度
2.2 spec diagram
一个点为一个方法实现;
满足方法spec的落在对应圈内,否则在圈外
spec越强的圈越小
2.3 好的spec需要具备的条件
-
内聚的
- 功能单一、简单、易理解
- 一条规约做一件事
/**
*找最长的字符串,并将它打印出来//这是两个spec,应该拆开
...
*/
-
信息丰富
- 应当使功能明确
比如“若Map中key不存在和key没有对应value均返回null”//这两种情况应该做出区分
- 应当使功能明确
-
信息足够强
- 比如“list2元素加到list1中,如果遇到null则停止”//这没有说清null前的到底是加入还是不加入
-
但是不可过于强
- 比如“effects:打开指定名的文件”
这太强了,不能保证一定有对应文件可以打开
- 比如“effects:打开指定名的文件”
-
尽量用abstract types
- 用List、Set等而不是具体到ArrayList等
- 不要限制到具体的DataType实现方式
比如:你可以要求client传入字符串,但是不要暴露处理的使用方式是String,List还是其他
-
pre-和post-的平衡
- private方法: ***可以不用pre- ***,而是在使用方法的位置check
- public方法:必须有pre-