Primitive types 基本数据类型(8种) |
|
Short、int 、long、float、double、boolean、char、byte |
如:String、BigInteger |
只有值,没有ID(无法与其他值区分),不能赋值为null; immutable |
有值,也有ID; 部分mutable,部分immutable |
在栈中分配内存,代价低 |
在堆中分配内存,代价高 |
静态类型检查 |
动态类型检查 |
(静态类型语言 如java) |
(动态类型语言 如python) |
提高程序的正确性和健壮性 |
|
关于"类型"的检查,不考虑值(不知道运行时会是什么值) |
关于"值"的检查 |
Immutable 不可变数据类型 |
Immutable 不可变数据类型 |
|
优点 |
优点:安全 |
优点:最少化拷贝以提高效率获得更好的性能,适合于在多个模块之间共享数据 |
缺点 |
缺点:频繁修改产生大量临时拷贝,需要垃圾回收· |
缺点:不安全 |
其他 |
一旦被创建,其值不能改变 对于引用类型,加final限制不能改变引用 |
安全地使用可变类型:局部变量(不涉及共享,且只有一个引用) 如果有多个引用(别名),不安全 Defensively copy 防御式拷贝:返回全新的对象 |
尽可能用immutable!
引用类型:圈住!
不可变类型(用双线椭圆),修改引用
可变类型:修改值
可变引用:单线箭头
不可变引用:双线箭头
是否可以相互替换
Spec变强:更放松的前置条件(前置条件更弱)+更严格的后置条件(后置条件你更强),
两条件同时变强或变弱则无法比较。
若规约强度S2>=S1,则可以用S2替换S1。
声明式规约更有价值!
内部实现的细节不在规约里呈现,而放在代码实现体内部注释里呈现。
规约定义一个区域,该区域包含所有可能的实现方式。
空间中的每个点表示一种方法的实现。
对于某个具体实现,若满足规约,则落在其区域内。
更强的规约表达为更小的区域。
客户端不喜欢太强的pre-condition,不满足precondition的输入会导致失败
So:不限定太强的precondition,而在postcondition中抛出异常:输入不合法,
fail fast,避免fail大规模扩散
是否使用前置条件取决于:
Pre-condition 前置条件(requires) |
Post-condition 后置条件(effects) |
@param |
@return @throws |
对客户端的约束 在使用方法时必须满足的条件 |
对开发者的约束 方法结束时必须满足的条件 |
契约:如果前置条件满足了,后置条件必须满足 |
|
除非在后置条件中声明,否则方法内部不应该改变输入参数。 尽量不设计mutating的spec,否则容易引发bugs。 尽量避免使用mutable对象。 避免使用可变的全局变量。 |
抽象类型:强调"作用于数据上的操作",程序员和client无需关心数据如何具体存储,只需设计/使用操作即可。
ADT由操作定义,与其内部实现无关。
可变数据类型:提供了可改变其内部数据值的操作;
不可变数据类型:其操作不改变内部值,而构造新的对象。(没有mutators)
ADT操作分类:
不利用该类型对象产生一个新的对象
可能实现为构造函数或静态函数(factory method)
用已有该类型对象产生新对象
如string.concat()(连接两个字符串,产生一个新的字符串)
如list.size()返回int(不同于原类型)
通常范围void,如果返回void,则必然意味着它改变了某些对象的内部状态
也可能范围非空类型(如容器类的put、add方法)
表示独立性:client使用ADT时无需考虑其内部如何实现,ADT内部表现的变化不应该影响外部spec和客户端。
如client能直接接触类成员变量。
表示泄漏影响表示不变量,也影响表示独立性:无法在不影响客户端的情况下改变其内部表示。
避免方法:private、final、defensive copy
ADT应保持其不变量在任何时候总是true;
ADT负责其不变量,与client的任何行为无关。
作用:保持程序的"正确性",容易发现错误。
表示空间R |
抽象空间A |
值的实际实现本质 |
抽象表示(client看到和使用的值) |
ADT实现者关注表示空间R |
用户关注抽象空间A |
R到A的映射
一定是满射:A中元素总有R中具体的实现
未必是单射:A中一个元素在R中可能有多重实现方式
未必是双射:R中表示不符合A中需求(如图中"abbc")
抽象函数AF:R和A之间映射关系的函数
AF:R->A
对于RI :R-> Boolean
RI:某个具体的"表示"是否合法;表示值的一个子集,包含所有合法的表示值;一个条件,描述了什么是"合法"表示值。
选择某种特定的表示方式R
进而指定某个子集是"合法"的(RI)
并为该子集中的每个值做出"解释"(AF)
即 如何映射
Safety from Rep Exposure
证明代码并未对外泄露其内部表示
保证不变量为true,不变量:
接口的成员变量默认用final关键字修饰,故必须有初值,可用public,default修饰,可用static修饰。
接口的方法只能被public、default、abstract、static、strictfp(严格浮点运算)修饰。
Strict inheritance 严格继承:子类只能添加新方法,无法重写超类(父类)中的方法(final限制)。
考虑final修饰类、方法、属性时的不同作用。
Override 方法:具有一致的signature,复用的基本机制。
三种多态:
用于function overloading(功能重载),即重载
泛型
(static dispatch 静态分派)并在编译阶段决定具体执行哪个方法(即对方法的调用取决于编译时声明的引用的类型)
而重写(dynamic dispatch 动态分派)则进行动态类型检查,根据运行时堆中的实例类型选择方法。
List<Integer> -> List
注意可能引起重载编译错误。
运行时不能用 instanceof 检查泛型。
不能用在静态变量
不能创建对象(不能new)
超类的子类型,如:ArrayList和LinkedList是List的子类型。
子类型的规约不能弱化超类型的规约。
避免向下类型转换。
Static dispatch 静态分派 |
Dynamic dispatch 动态分派 |
将调用的名字与实际方法的名字联系起来(可能有多个) |
决定具体执行哪一个操作 |
重载,在编译阶段即可确定执行哪个具体操作 |
重写,在运行时决定 |
Early/static binding |
Lade/dynamic binding |
绑定static、private、final方法时发生 |
重写父类子类的同样方法 |
比较内存地址ID
用于比较基本数据类型
验证正确性:reflexive 自反性、symmetric 对称性、transitive 传递性、非空(a.equals(null) return false)
等价的对象必须有相同的hashCode
Rule:重写equals时重写hashcode
Observational equality 观察等价性 |
Behavioral equality 行为等价性 |
在不改变状态的形况下,两个mutable看起来是否一致 |
调用对象的任何方法都展示出一致的结果 |
调用observer,producer,creator |
调用任何方法,包括mutator |
当前情况下,看起来(如成员变量)相同 |
经过改变后,依然相同(只是别名引用) |
对不可变类型,观察等价性和行为等价性完全等价。
对可变类型,往往倾向于实现严格的观察等价性。(但有时观察等价性可能导致bug,甚至破坏RI)
对可变类型,应当实现行为等价性,即只有指向内从空间中同样的objects才相等(即equals比较引用,如==而hashcode把引用映射为一个值)。
所以对可变类型,无需重写equals和hashcode,直接继承object。(比较引用)
若一定要判断两个可变对象是否一致,最好定义一个新的方法。
原文:https://www.cnblogs.com/standingby/p/9206630.html