第四章到第八章
基本数据类型、对象数据类型
- 基本数据类型:
- int
- long
- boolean
- double
- char
- 对象数据类型
- Object
- 常见的对象类型:String,BigInteger
- 基本类型和对象类型的特点比较
基本类型 | 对象类型 |
---|---|
一定不可变 | 可以可变 |
在栈中分配 | 在堆中分配 |
只有值 | 除了值还有ID |
代价低 | 代价高 |
静态、动态类型检查
- 语言类型
- 静态类型语言
- 所有的变量,根据声明和定义,在编译时类型可以确定的语言
- 动态类型语言
- 在运行阶段进行类型检查
- 静态类型语言
- 检查类型
- 静态检查:运行前自动的发现错误
- 语法
- 类名、函数名
- 参数数目
- 参数类型
- 返回值类型
- 动态检查:运行时自动的发现错误
- 非法参数值
- 非法返回值
- 数组越界
- 访问空指针
- 静态检查:运行前自动的发现错误
- 总结
-
静态检查是基于类型的检查,错误与变量实际数据无关
因此静态检查无法检查出由于数值导致的而非类型导致的错误 -
动态检查能够弥补静态检查的缺陷
-
不论是动态类型语言还是静态类型语言都可以执行 两种 类型的检查
-
Mutable/Immutable
- immuntable(对象数据类型而言,不考虑基本类型)
- 变量不可变:
不能使用 赋值 语句改变 变量 指向的存储空间
因此,每一次 使用=
进行 赋值 操作,都是新建了一个对象
可以使用final
关键字实现 - 类型不可变:
一旦不可变类型被创建,其数值不可改变
即该对象类型没有可变方法或不支持可变操作
- 变量不可变:
- mutable(只能是对象数据类型)
- 存在改变引用内存数值的方法
- 改变时不需要拷贝,可以提高效率,适合共享数据
- 风险和应对方式:
- 可变函数参数:
- 原因:当传入可变类作为参数时,该方法有可能修改这个可变类,导致出错
- 方法:尽量不要在函数内修改参数,或者传入不可变对象
- 返回可变参数:
- 原因:返回的可变类可能是类的成员变量,用户的修改行为会破坏RI
- 方法:防御拷贝,返回不可变对象
- 可变函数参数:
Snapshot diagram
- 是一种展示变量随时间变化的图示
-
基本类型的变量:使用 单实线 指向一个数据
-
可变对象 的表示和改变:使用 单实线 指向 单实线圈
-
不可变对象 的表示和改变:使用 单实线 指向 双实线圈
-
final变量:使用 双线箭头
-
Specification(前置/后置条件)
- 规约由前置条件和后置条件组成,
- 若前置条件满足,则后置条件必须满足
- 前置条件不满足,可以做任何事情,推荐"快速失败"
- 在规约中使用不可变对象有利于减低复杂度
- 前置条件:
- 对客户端的约束,在使用方法时必须满足的条件
- 关键字:requires
- 后置条件
- 对开发者的约束,在方法结束时必须满足的条件
- 关键字:effects
行为等价性
- 行为等价性 是规约的一种具体表现形式,如果两个实现符合规约,则等价
规约的强度
- S 2 ≥ S 1 S_2\geq S_1 S2≥S1: S 2 S_2 S2的前置条件弱于 S 1 S_1 S1的前置条件,后置条件相同
- S 2 ≥ S 1 S_2\geq S_1 S2≥S1: S 2 S_2 S2的后置条件强于 S 1 S_1 S1的前置条件,前置条件相同
- S 2 ≥ S 1 S_2\geq S_1 S2≥S1: S 2 S_2 S2的前置条件弱于 S 1 S_1 S1的前置条件 and 在满足前置条件的情况下, S 2 S_2 S2后置条件强于 S 1 S_1 S1的前置条件
ADT的四种操作类型
- Creators: 即构造方法,从无到有
public ClassName(parameter.../null)
- Producers: 从旧到新
public ClassName producer(parameter.../null)
- Observers: 只是取数据
public ImmutableType getElement()
- Mutators: 只有在mutable的ADT中才有,能够改变该ADT的数值
public Boolean/void setElement(parameter.../null)
表示独立性、表示泄露、不变量
- 表示独立性(Representation Independence):
- RI定义为ADT的功能和使用与其内部的字段和算法(表示)无关这样一种性质
- RI能够使得客户端在符合语法下使用ADT,实现者修改ADT的表示之后,客户端代码工作正常
- RI是通过规约实现的,规约能够排除表示的因素,创造出client可以依赖的内容
- 不变量(Invariant)
- ADT在运行的任何时候都能保持不变的一种属性,比如immutability
- ADT的Invariants由ADT负责,与任何的client的行为都无关
- 表示泄露(representation exposure)
- 例子:在类代码的外部能够直接修改类的字段
- 表示泄露会破坏表示独立性,也会影响不变量,无法在不影响客户的情况下修改内部表示
- 总结
- 不要将mutable参数包括到类中
- 返回immutable或者mutable类的不可变视图。
- 使用immutable
表示空间、抽象空间、AF、表示不变量
-
表示空间®:
- 表示值构成的空间,即所有字段的所有可能值构成的空间。
所有字段的笛卡尔乘积,例如一个类如下,则表示空间如下:
public class Demo{ private int a; private boolean b; }
N ( i n t ) × N ( b o o l e a n ) N(int)\times N(boolean) N(int)×N(boolean)
- 实现者可以直接修改表示空间
- 表示值构成的空间,即所有字段的所有可能值构成的空间。
-
抽象空间(A):
- 抽象值构成的空间,设计该ADT的所支持的抽象值
-
抽象函数(Abstract Function):
- AF指如何将 表示空间 中的每一个值,解释为 抽象空间 的每一个值
- AF一定是是满射,不一定是单射,所以必然存在 某些表示值 不能映射 成为 抽象目标值
-
表示不变量(Rep Invariant)
- ADT的一个重要的不变量(Invariant),ADT在运行时,RI必须时刻为真
- RI是一个 R − > B o o l e a n R->Boolean R−>Boolean的一个映射函数, 当且仅当 r可以被AF映射为a 的时候为true
- RI规定了哪些表示空间的数值可以映射为抽象空间
-
关系
- R是定义域,A是值域,AF是一个映射函数
- 相同的R,可以有不同的RI
- 相同的R,RI,可以有不同的AF,映射成为不同的A
- checkRep()方法使用assert进行RI的检查
注释撰写AF、RI、Safety from Rep Exposure
- AF
- 明确的R到A的映射关系,使用自然语言即可
- RI:
- 每一个field需要满足的限定条件
- 多个field之间的关系的限定条件
- Safety from Rep Exposure
- 检查各个字段是否私有
- 检查处理字段的方法的参数,返回值
- 证明代码未对外泄露自己的Rep
- Spec
- 规约中 不能写 R空间和AF和RI的内容,只能写 A空间的数据
- 不变量的保证的标准
- 使用creator和producers生成的时候保持true
- 使用mutators和observer的时候保持true
- 表示未泄露
接口、抽象类、具体类
- 接口
- 传统的接口中只有方法的定义没有实现
- java的接口中可以有 默认(default关键字)的实现,亦可以有成员变量
- 接口可以被接口单继承,被类多实现
- 抽象类
- 在普通类的基础上增加了抽象方法的类,一旦出现抽象方法,则该类必定是抽象类
- 抽象类必须要使用关键字abstract修饰,抽象方法必须用abstract修饰
- 抽象方法没有实现,只有定义
- 不能实例化抽象类
- 具体类
- 可以具体实例化的类
- 作用域
作用域 | 当前类 | 同一包内 | 子孙类(不同包内) | 其他包 |
---|---|---|---|---|
public | yes | yes | yes | yes |
protect | yes | yes | yes | no |
default | yes | yes | no | no |
private | yes | no | no | no |
继承(override)
- 继承
- 用于实现抽象/具体类与抽象/具体类,接口与接口之间的复用修改关系,但是不能是普通类继承抽象类
- 使用extend关键字进行继承,java只有单继承
- 重写
- 在继承的时候,使用@Override记号,修改父类函数的情况
- 重写的函数,完全相同的函数签名,包括函数名、参数类型列表、返回值
- 返回值、可见性、异常,必须满足 LSP原则
- 运行时 决定采用哪个函数
多态(overload)
- 多态
- 函数重载
- 泛型
- 子类替换父类
- 重载
- 多个方法(同一个类或者不同类)函数名字相同,但是 参数类型列表不相同 的情况
- 返回值、可见性、异常,都是可以不同,但是如果只有这个不同则编译错误
- 可以在 编译时,根据变量的 类型,决定函数的调用
泛型
- 一个类或者接口声明了泛型变量,则是泛型,具体代码如下
class ClassName <T,E,..>{
private T a;
public E b;
}
equals和==
-
==
的引用等价性- 对于基本数据类型,两个变量 数值 相等
- 对于对象数据类型,两个变量 地址 相等
-
equals
对象等价性- 基本数据类型没有
equals
- 对象数据类型的
equals
方法在没有被重写时是==
equals
函数基本写法
@Override public boolean equals(Object o){ if (!(o instanceof ClassName)){ return false; } ClassName that = (ClassName) o; return your equals logical }
instanceof
关键字- 可判断
null
- 可判断类名
- 是动态检查,不是静态,除了
equals
建议少用
- 可判断
- 基本数据类型没有
hashCode()和equals()
equals
自定义逻辑要满足以下要求- 自反
- 传递
- 对称
hashCode
自定义逻辑要满足以下要求equals
为true
的对象,hashCode
必须相等equals
为false
的对象,hashCode
可以相等,但是性能不好hashCode
对于未发生变化的对象必须不可变
可变/不可变对象的等价性
- 可变对象的等价性
- 观察等价性:
- 如果不改变对象,任何观察都相同,则观察等价
- 对于只调用
observer
、producer
和creator
都是一样的结果
- 行为等价性:
- 在改变对象的情况下,各种操作都相同
- 调用各种方法,包括·
mutator
,结果都相同
- 可变对象偏向使用观察等价性,例如JDK中的
List
不同List的实现,只要 元素相同,则equals
但是JDK中不同的mutable类使用的等价性不同,StringBuilder则是使用行为等价性 - 总结
- 可变类型使用行为等价性,不用重写
equals
和hashCode
- 如果要判断观察等价性建议写
similar
方法重新判断
- 可变类型使用行为等价性,不用重写
- 观察等价性:
- 不可变对象的等价性
- 必须重写
equals
和hashCode
- 必须重写