1.数据类型和类型检查
基本数据类型:
int, long, byte, short, char, float, double, boolean一类
只有值,没有ID (与其他值无法区分),不可变
在栈中分配内存,代价低
对象数据类型:
Classes, interfaces, arrays, enums, annotations
既有ID,也有值,可变/不可变
在堆中分配内存,代价昂贵
静态类型检查:
- 关于“类型的检查”,不考虑值
- 在编译阶段发现错误,避免将错误带入运行阶段
- 提高程序的正确性、健壮性
- 静态类型检查错误:
- 语法错误
- 类名/函数名错误
- 参数数目错误
- 参数类型错误
- 返回值类型错误
动态类型检查
- 关于“值”的检查
- 动态类型检查错误:
- 非法的参数值
- 非法的返回值
- 越界
- 空指针
Mutable可变 & Immutable不可变
不变对象:一旦被创建,始终指向同一个值
可变对象:拥有方法可以修改自己的值/引用
- String 不可变
- StringBuilder 可变
用Snapshot表示String和StringBuilder的区别
mutable 优点:
- 拷贝:不可变类型,频繁修改会产生大量的临时拷贝,需要垃圾回收;可变类型,最少化拷贝,以提高效率
- 获得更好的性能
- 模块之间共享数据
final
- 尽量使用
final
作为方法的输入参数、作为局部变量 final
表明了程序员的一种“设计决策”final
类无法派生子类final
变量无法改变值/引用final
方法无法被子类重写
值的改变 & 引用的改变
- “改变一个变量”:将该变量指向另一个值的存储空间(引用)
- “改变一个变量的值”:将该变量当前指向的值的存储空间中写入一个新的值
表示泄露和防御式拷贝
通过防御式拷贝,给客户端返回一个全新的对象(副本),客户端即使对数据做了更改,也不会影响到自己。大部分时候该拷贝不会被客户端修改,可能造成大量的内存浪费,如果使用不可变类型,则节省了频繁复制的代价
基本类型的值
对象类型的值
可变对象:单线圈
不可变对象:双线圈
不可变的引用:双线箭头
可变的引用:单线箭头
- 引用是不可变的,但指向的值却可以是可变的
- 可变的引用,也可指向不可变的值
List
Set
Map
2.设计规约
Spec概念
程序和客户端达成的一致
作用:
- 给“供需双方”都确定了责任并区分责任,调用时双方都要遵守(客户端只需要理解Spec即可)
- 隔离“变化”、降低耦合度
- 不需要了解具体实现
要素:
- 输入数据类型(客户端约束)
- 输出数据类型(内部实现约束)
前置条件 & 后置条件
- 前置条件:For 客户端
- 后置条件:For 开发者
- 契约:
前置条件满足了,后置条件必须满足;
前置条件不满足,后置条件不一定满足(输入错误,可以抛出异常)。
行为等价性
- 站在客户端角度、根据规约:功能是否等价
Spec的写法
- 方法注释
@param
@return
@throws
- 输入类型、返回类型
Spec应有的要素:
- 内聚的
Spec描述的功能应单一、简单、易理解
规约做了两件事,所以要分离开形成两个方法。 - 信息丰富的
不能让客户端产生理解歧义 - 足够“强”
太弱的spec,客户不放心
开发者应尽可能考虑特殊情况,在post-condition给出处理措施 - 足够“弱”
太强的spec,在很多特殊情况下难以达到,给开发者增加了实现的难度(client当然非常高兴)
Spec的强度
- 前置条件越弱,规约强度越强;
- 后置条件越强,规约强度越强;
- 规约越强,开发者责任越重,客户端责任越轻;
- 某个具体实现,若满足规约,则落在其范围内;否则,在其之外。
- 程序员可以在规约的范围内自由选择实现方式;
- 更强的规约,表达为更小的区域;