基本数据类型:
char、int、float、double等,空间在栈中进行分配
对象数据类型(引用数据类型):
对象类型Object type,包括String以及专门设计给基本数据类型的包装类:Boolean、Integer、Short、Long、Character、Float、Double
只能通过在栈中存储的地址进行操控。
Static checking:对类型的检查
- 语法错误
- 类名/函数名错误(没检查到field或method)
- 参数数目错误
- 参数类型错误
- 返回值类型错误
Dynamic checking:对值的检查
- 非法参数值(除0错误)
- 需要注意,浮点数除0只会返回错误值,不会产生异常
- 非法返回值
- 越界访问
- 空指针
对变量和变量值:
改变变量等价于将该变量指向另一个存储空间
改变变量的值等价于将该变量当前指向的存储空间中写入一个新值
Immutable types:
- Value不可改变的类型:String、Integer……
一旦赋初值,其值不发生改变
- Java中将这一概念泛化到了对象:对一个对象而言,Value不可变 ó引用不变
一旦确定其指向的对象,不能再改变其指向对象
Final:(体现一种决策体现,其规则检查将由编译器进行)
Final可修饰基本数据类型or对象数据类型。使用final将开启编译器对变量的检查,若检测到第一次赋值/引用之后的再次变化,将提示错误。
Final可修饰方法。被final修饰的方法无法被重写。
Final可修饰类。被final修饰的类无法被继承
防御式拷贝:一种针对mutable对象的保护设计,需要人工实现
对于mutable对象,由于其本身特点,一旦在程序中出现多个指向它的引用,对其更改很容易造成意想不到且难以发现的错误,因此使用防御式拷贝的手段,即返回一个内容相同但地址不同的对象,即使最终发生更改,也不会在其它方法中暴露出来
这种方式有个缺点,容易产生大量的内存浪费。
另一种方法:包装器(在运行阶段进行,无法进行静态检查)
基本类型的值/对象类型的值:
可变对象/不可变对象:
可变/不可变引用:
Specification:
- l 类型:
- n 静态类型声明(方法签名):根据语法给出输入、返回值、和可能抛出异常的信息
- n 方法前的注释
- l -precondition
-postcondition
-Exceptional behavior
- l 需要隐藏本地变量
测试用例的编写必须仅依据规约进行,eg.int find(int num)函数返回num所在的索引,即使在实现时我们采用的方法是返回最后一个索引,也不能在测试代码编写时。
规约的分类:
- l 确定性:
- l 陈述性:
- l 强度:
行为等价性:以client对两个函数,如何判断两种实现方式是否是等价的?(思考什么是行为等价性)
以client的视角来说,它所知道的只有函数的签名和注释,因此其行为等价性的判别需要关注:
- l 函数签名(输入、返回、异常类型是否一致)
- l 根据spec来判断是否对相同input,能得到相同的output
- n Eg. 在数组中寻找一个数的索引,即使所找到的索引不同,但若对return的说明,仅声称它代表着一个匹配该数的索引,则仍认为两者行为等价
规约的强度:(更强的规约,限制更少、满足更多)
- Precondition(对输入的要求)更弱 || Postcondition(对输出的要求)更强,说明spec更强
- 存在这样一种情况,在对比两个规约的时候,当比较出两者precondition强弱的时候,postcondition的判断应在最弱precondition的限制下进行比较
规约的画法:
- l 满足规约的所有实现(无法具体表示),用一个矩形表示
- l 满足规约的某一个实现
- n Precondition越弱→可能的输入越多→实现的自由度越小→圈越小
- n Postcondition越强→保证的东西越多→实现的自由度越小→圈越小
ADT操作类型:
Creators:构造器——从无到有
Producers:生产器——从有到新
Observers:观察器
Mutators:变值器——改变对象属性
表示独立性:ADT的保持的一种特性,client无需知道具体实现即可使用,implementor进行改变无需通知client,只需保证spec即可。
表示泄露:调用class的代码可通过实例化直接修改原先class的内部表示,从而使得部分client(可能)对class的这些内部实现产生依赖。
RI:Rep Invariant,表示不变量,定义了所有field可能的取值中,哪些合法,哪些不合法
AF:给出解释field所有合法取值的办法
等价性:
如何理解等价性?
等价性的判断是基于两个对象来进行的,脱离两个对象来谈等价没有意义
等价性的判断可以有不同的标准,如接下来的AF和Observation
ADT的理解:
ADT是对数据的抽象,体现为对一组数据的操作
ADT的抽象通过对AF的设计进行实现,AF将ADT中具体的表示抽象成对外的表示
外界对ADT进行的各种操作均由ADT提供,因此其展示出来的结果由ADT确定
等价性类型:
等价性类型:
AF等价性:根据AF的映射,来比较两个对象的内部表示是否一样
观察等价性:通过对对象所有的Observer进行调用,并根据观察结果,判断两对象是否等价
引用等价性:”==”操作符进行的判断,判断两个引用对象的地址是否相等或两个基本类型的值是否相等
对象等价性:”equal(Object o)”,Object类本身具有的equal()进行引用等价判断,因此需要在子类中重写该函数,以满足implementor对等价性的要求。对象等价性的定义由implementor来去确定。
行为等价性:对两个对象,调用两个对象的任何方法都能展示出一样的结果
Immutable对象:
对象等价性:由于其对象不可变,因此倾向于重写equal()来实现其等价性
Mutable对象
观察等价性:在对于一个mutable对象,往往倾向于实现严格的观察等价性
行为等价性:对mutable对象而言,引用地址相同即可保证这一等价性,因此其equal往往无需重写
Equal()函数:
最开始事项的位置在Object类中,原型为equal(Object o)
在子类中要对equal()进行重写时需要注意,使用的是重载还是重写,重写要求形参是Object对象,否则实际进行的是重载
若目的是重写该函数,最好使用@Override进行修饰,交给编译器进行检查
必须满足等价关系:
自反性
传递性
对称性
对两个对象,在不改变其value的情况下,多次调用equal返回值应一样
Hashcode和equal函数的关系
Hashcode和equal本身并无关系,hashcode是在Set、List、Map这些本质数据结构是散列表的Collection子类中进行使用的
一些神奇的机制
Integer的-128~127,Java提供了包装类来对将一个整型数转换成一个对象,若该Integer对象代表的数落在[-128,127]之间,除非对象是new出来的,否则该对象的地址保存在一个常量池中(cache数组),此时无论多少个Integer对象,它们都有着相同的引用地址
等价性的小结:
- 等价性需要考虑是否满足等价关系
- 一个对象的等价性必须与其hashcode相吻合
- AF是不可变类型等价性的基础
- 引用等价性是可变类型等价性的基础
- Final Rule:
- 对于不可变类型必须重写equals()和hashCode()函数
- 对于可变类型完全不必重写其equals()和hashCode()函数