文章目录
一、数据类型与类型检验
1、数据类型
(1)基本数据类型:int,long,boolean,double。char,float
- 只有值,没有ID
- immutable 不可变的
- 在栈中分配内存
(2)对象数据类型:String,Integer,classes,arrays,enums(枚举)
- 既有值,也有ID
- 一些mutable,一些 immutable
- 在堆中分配内存
2、类型检查
(1)静态类型检查:编译阶段进行
- 例子:语法错误,类名、函数名错误,参数数目错误,参数类型,返回值类型错误
- 关于“类型”的检查,不考虑值
(2)动态类型检查:运行阶段进行
- 非法的参数值,非法的返回值,越界,空指针
- 关于“值”的检查
静态类型检查优于动态类型检查
3、可变数据类型和不可变数据类型
(1)不可变数据类型:一旦被创建,其值不改变
- 如果是引用类型,也可以是不变的:一旦确定指向的对象,不能再改变。eg:final Person a = new Person(“Rose”)
- 例子:String s = “a”;s = s.concat("b”);s指向新的地址和值
【note】final:
final变量无法改变值、引用
final类无法派生子类
final方法无法被子类重写
(2)可变数据类型:StringBuilder,Date
- 可变对象拥有方法可以修改自己的值/引用
- 例子:StringBuilder sb = new StringBuilder(“a”);sb.append(“b”);sb仍然指向原地址,但是值改变了
(3)可变数据类型和不可变数据类型区别
- 不可变数据类型使用时,频繁修改产生大量临时拷贝。垃圾回收,降低性能。不可变更安全
可变数据类型较少拷贝,提高效率,有更好性能,适合于多模块之间共享数据
(4)安全使用不可变数据类型
- 防御式拷贝,返回全新对象eg:return new Date(groundhogAnswer.getTime());
- 使用局部变量,不会涉及共享
- 只是用一个引用
4、Snapshot图
用于描述程序运行时的内部状态
- 引用是不可变的,但指向的值可以变化。可变的引用也可以指向不可变的值。
如下图,不可变引用sb可以改变指向的值,但是引用不可改变
5、复杂数据类型
(1)Array数组
- int[]
(2)List列表
- Snapshot图:圆角矩形表示
(3)Set集合
(4)Map
(5)迭代
- 显示迭代
for(int i=0;i<n;i++){
cities.get(i);
} - 隐式迭代
for(interger i : cities){
System.out.println(i);
} - 迭代器
List<String> lst = …;
Iterator iter = lst.iterator();
while(iter.hasNext()){
String t = iter.next();
System.out.println(t);
} - 迭代时容易出现的错误
使用remove时,修改原列表中的值
如以下出现错误:
List<String> lst = …;
Iterator iter = lst.iterator();
while(iter.hasNext()){
String t = iter.next();
if(t.startsWith(“6.”)){
list.remove(t);
}
}
首先,remove 第一个。此时lst为{“6.005”,“6.813”}。lst.get(0)=“6.005”。迭代器指向“1”,然而,lst.get(1)=“6.813”
迭代器指向“1”,remove “6.813”。剩余一个“6.005”
采用下述:
while(iter.hasNext()){
String t = iter.next();
if(t.startsWith(“6.”)){
iter.remove();
}
}
6、不可变数据类型例子
(1)基本数据类型都是不可变的
(2)复杂数据类型中不可变的:List,Set,Map
但是 ArrayList,HashMap可变
(3)包装
Collections.unmodifiableList
Collections.unmodifiableSet
Collections.unmodifiableMa
- 这种包装,编译阶段无法据此进行静态类型检查
二、设计规约
1.规约作用
可以隔离变化,无需通知客户端。防火墙角色
2、行为等价性
两个行为是否可以相互替换
根据规约判断行为是否等价,如果符合规约,则等价
站在客户端角度
3、spec构成
(1)前置条件precondition:关键词requires
(2)后置条件postcondition:关键词effects
(3)契约:如果前置条件满足,则后置条件必须满足;前置条件不满足,则方法可以做任何事情
(4)规约:
- 静态类型声明是一种规约
- 方法前的注释也是规约
/**
*@param 参数,前置条件
*@return 返回值,后置条件
*@throws 异常
**/
4、设计spec
(1)规约强度:
前置条件更弱,后置条件更强的规约强度大
(2)规约分类
- 确定的规约:给定一个输入,输出唯一
- 欠定的规约:一个输入可以有多个输出
- 非确定的规约:同一个输入,多次执行得到不同输出
- 操作式规约:伪代码
- 声明式规约:没有内部描述,只有初始和终止状态。内部细节在内部注释中
【声明式规约更有价值】
三、ADT
1、opertations
(1)creator构造器:构造一个新的同类型对象 。 t*->T
(2)producer生产器:从本对象生成一个新的同类型对象,T+,t*->T
(4)observer观察器:返回一个其他类型的对象,T+,t*->t
(5)mutator变值器:改变对象属性的方法,T+,t*->T|t|void
2、表示独立性
客户使用ADT时无需考虑内部如何实现,ADT内部表示变化不影响外部的spec和客户端
3、测试
(1)测试creator,productor,mutator:调用observer观察结果是否满足spec
(2)observer:调用creator,productor,mutator等产生或改变对象,看结果是否正确
4、RI,AF
(1)invariants保持不变量
- 不变量:在任何时候都为true
- 保持程序的“正确性”,容易发现错误
(2) rep exposure 表示泄露:影响不变形和辨识独立性
- 使用防御式拷贝来避免
- 使用immutable彻底避免泄露
(3)Rep invariant 和 Abstraction function
- R:表示空间 ,开发者看到的 。 A:抽象空间,客户端看到和使用的
- abstraction function:抽象函数 。AF: R->A。满射,未必单射
// AF(属性1,属性2,…) = 客户端看到的抽象空间 - Rep invariant:表示不变性。
某个具体表示是否合法
所有表示值的一个子集,包含了所有合法值
一个条件,描述什么是合法的表示值
(3)checkRep():随时检查RI是否满足
- 在所有可能改变rep的方法内检查:creator,productor,mutator
- observer方法可以不用,但最好检查
(4)有益的可变性beneficial mutator:对immutable的ADT来说,在A空间的abstract value不可变,但是内部表示的R空间的取值可以变化。但不是immutable的类中可以随便出现mutator!!取决于AF
如下图的AF,此时两个属性可以改变(约分),只要A不变
5、撰写
精确记录AF:解释每个R值
精确RI: 所有属性什么时候有效,即多个属性之间的关系
//Abstraction Function:
//…
//Rep invariant:
//…
//Safety from rep exposure:
//…
【区分】
spec规约:客户端看到的
rep:表示空间,只有程序员看到
A:抽象空间,客户可见
四、OOP
1、Interface接口
- 确定ADT规约
- 接口之间可以继承和扩展
- 一个类可以实现多个接口
- 一个接口可以有多个实现类
- 实现类要实现接口所有方法,但可以增加新方法
public interface Person{
定义方法…
}
【注】无法保证所有实现类中的方法全部是接口中定义的,因此客户端需要知道某个具体类的名字
- default方法:在接口中使用default修饰方法,实现类中可以直接使用
default int method(int a){…}
2、继承inheritance
子类 extends 父类
- 严格继承:子类只能添加新方法,无法重写父类中的方法(父类方法final修饰)
可重写继承:子类可以重写
(1)Override:重写函数完全相同的函数返回值,函数名,参数,运行时决定执行哪个方法
- 父类中被重写的函数不为空,子类可重写也可不重写
- 父类函数为空,所有子类必须重写
- super.方法名,调用父类函数
(2)abstract 抽象类
- 某些方法所有子类型都有,但是彼此有差别,在父类型中使用抽象方法
public abstract class Person{
int x,y;
public void Method1(int a){…}
…
public abstract void draw();
}
3、多态,子类型、重载
(1)多态分类:
- 特殊多态:功能重载(方法名相同,参数类型不同)
- 参数化多态
- 子类型多态
(2)overload重载
- 多个方法具有同样名字,但是参数和返回值不同
- 编译时决定使用哪个方法
- 参数类型一定不同,返回值可以相同也可以不同
- overload也可以发生在父类和子类之间
【overload和override】重载overload在编译时决定使用哪个方法,重写override在运行时决定使用哪个方法
(3)泛型编程
- 使用<>来表示
List<Integer> ints = new ArrayList<>();
public interface List<E>
public class Pair<E>{
private final E first;
…
} - 通配符?
List<?> list = new ArrayList<String>();
List<? extends Animal> : Animal的所有子类
List<? super Animal> :Animal的所有父类
(4)子类型多态:不同类型的对象可以统一处理不用区分
五、ADT/OOP中的等价性
1、不可变ADT中的等价性
判断方法:
(1)使用AF判断,映射到同一个A,则等价
(2)使用observer方法,返回结果相同
- 不可变类型要重写equals和hadCode
2、==和equals
(1)==是引用等价性,指向同一地址
- 基本数据类型
- 判断两个对象时使用==,判断ID内存空间是否相同
(2)equals是对象等价性
- 对象类型
- 在object中缺省的equals()是判断引用等价性,相当与==
自定义ADT时,要重写equals方法
(3)hasCode():哈希表中的值
- 等价的对象必须映射到同一哈希表中
- 重写equals()后紧跟重写hasCode()除非保证ADT不会放在Hash类型的集合类中
(4)clone
- x.clone() != x
- x.clone().getClass() == x.getClass() // 获得一个实例的类型类。
- x.clone().equals(x)
(5)Integer 使用==和equals
- Integer 中-128~127 可以用==,其他判断相等要用equals
3、可变ADT中等价性
(1)观察等价性:不改变状态时,连个对象是都看起来一致
- 有时候观察等价性会导致bug
(2)行为等价性:调用任何方法展示出一致结果
- 对可变类型,无需重写equals和hasCode