Specification、前置/后置条件:
方法的规约:
前置条件(requires):对客户端的约束,在使用方法时必须满足的条件。
后置条件(effects):对开发者的约束,方法结束时必须满足的条件。
如果前置条件满足,则后置条件一定要满足。如果前置条件不满足,可以做任意事。
静态类型声明是一种规约,可以根据此进行静态检查。
方法前的注释也是一种规约,但是需要人工判定是否满足。
前置条件看@param,后置条件则观察@return和@throws。
一个规约,应该关于参数和返回的值,但是不应该关于局部变量和私有区域。
可变/不可变方法的规约:一定要体现出这个方法是否可变:
除非后置条件中声明过,否则方法内部不应该改变输入参数。
行为等价性:
两个函数对用户来说是否等价。客户端视角看行为等价性。
根据规约判断是否行为等价。
单纯看实现代码,并不足以判定不同,implementation是否行为等价,必须结合spec判定行为等价性。
规约的强度:
如何判断哪个规约更好:规约的确定性,规约的陈述性,规约的强度
如何比较两个规约,以判断是否可以用一个规约替换另一个。
spec变强:更放松的前置条件+更严格的后置条件
再判定后置条件是否更强的时候,我们需要限定较强的前置条件下观察两个规约的后置条件:
ADT 操作的四种类型:
ADT:强调作用于数据上的操作,程序员和client无需关心数据如何存储,只需要设计/使用操作即可。
构造器:新创建一个对象
生产器:在已有的对象上创建一个对象
观察器:look look
变值器:only mutable
构造器:可能是构造函数,像new ArrayList();或者仅仅是一个静态函数,如Arrays.asList();
一个静态的构造器可以被称为工厂方法
String.valueOf(Object Obj)就是一个工厂方法
变值器:通常返回void,也有可能返回非空类型如Set.add()返回一个Boolean。
表示独立性(Representation Independence) 表示泄露
表示独立性:用户在使用ADT时不需要考虑内部如何实现,ADT内部表示的变化应该影响外部的规约和客户端。
表示泄露:ADT中的数据被外界获取。
ADT测试:
测试creators,producers,and mutators:调用observers来观察这些operations的结果是否满足spec;
测试observers:调用creators,producers,and mutators等方法产生或改变对象,来看结果是否正确。
不变量、表示不变量 RI:
保持不变量:immutable就是一个典型的不变量
由ADT来负责不变量,与client端的任何行为无关。
Private可以让变量域和方法只有再一个类中被访问。
Final可以保证变量域不发生改变。
除非迫不得已,否则不要把希望寄托于客户端上,ADT有 责任保证 自己的invariants,并避免“表示泄露”。
用immutable类型彻底避免表示泄露。
表示空间、抽象空间、AF:
R:表示空间
A:抽象空间
ADT开发者关注表示空间R,clien关注抽象空间A
一定是满射,但未必是单射
AF:R->A
RI:R->Boolean
表示不变性RI:某个具体的“表示”是否合法
在所有可能改变rep的方法内都要检查(creators,producers,mutators)
利用checkRep()。
有益的可变性:改变R值,但是一定不可以改变A值!!!
这 种 mutation 只是改变了R值,并未改变 A 值,对 client来说是immutable 的->“ AF 并非单射”,从一个R值变成了另一个R值。
以注释的形式撰写 AF、RI:
在代码中用注释形式记录AF和RI
要精确记录RI:rep中的所有fields何为有效
要精确记录AF:如何解释每一个R值
rep exposure safety argument表示泄漏的安全声明:给出理由来证明代码并未对外泄露其内部表示。
ADT的规约只能使用client可见的内容来撰写,包括参数、返回值、异常等。
如果规约里需要提及“值”,只能使用A空间中的值。
接口、抽象类、具体类:
实例方法和类方法的区别:类方法前有static。
Interface和Class: 定义和实现ADT
接口之间可以继承与扩展
一个类可以实现多个接口(从而具备了多个接口中的方法)
一个接口可以有多种实现类
Default方法:接口中每个方法都要在所有类中实现,用default可以统一实现某些功能而无需在各个类中重复实现它。
接口中不应该存在构造器,但是可以有工厂方法代替构造器:
抽象类:
一个类至少有一个抽象方法(没有实现的方法)
如果某些操作是所有子类型都共有,但彼此有差别,可以在父类型中设计抽象方法,在各子类型中重写。
所有子类型完全相同的操作,放在父类型中实现,子类型中无需重写。有些子类型有而其他子类型无的操作,不要在父类型中定义和实现,而应在特定子类型中实现。
继承,overriding
如果一个方法不能被重写,要加上final。
子类只能添加新方法,不能重写超出类中的方法
重写的函数:完全相同的signature
实际执行调用哪个方法运行时决定
重写之后可以利用super()复用父类型中函数的功能,并对其进行了扩展
多态、子类型、重载:
特殊多态:功能重载
参数化多态:
子类型多态
重载:多个方法具有同样的名字,但有不同的参数列表或返回值类型
价值:方便 client 调用, client 可用不同的参数列表,调用同样的函数
方法重载是静态多态。
必须是不同的参数列表,其他可以相同也可以不同。
注意参数列表中,参数名称是可以不同的,但是参数类型相同也算相同。
重写类型的方法在运行是确定数据类型,但是重载类型的方法在编译时就确定了数据类型。
泛型:
用<>来声明变量类型的时候可以采用泛型。
- 泛型的接口,非泛型的实现类
- 泛型接口,泛型的实现类
通配符:只能在使用泛型时出现
运行时泛型擦除
子类型的规约不能弱化超类型的规约
子类型多态:不同类型的对象可以统一的处理而无需区分
instanceof():判断类型是否相同,相同返回true
重写toString:
我们需要让toString的输出满足格式: