前言:内容目标:ADT,OOP
Section 1 ADT
一.抽象类型
1.传统类型定义关注数据的具体表示,而抽象类型强调“作用于数据上的操作”,程序员和用户端无需关心数据如何具体存储的,只需设计/使用操作即可。
2.抽象类型是不透明的,具体实现不可见,而是用操作和规约刻画类型特征
二.数据类型与操作的分类
1.数据类型分类:可变与不可变数据类型
可变数据类型:提供了可改变其内部数据的值的操作
不可变数据类型: 其操作不可改变内部值,而是构造新的对象。
关于可变性与不可变性的详细区分已经在Part 1中介绍,这里不再阐述
2.操作的分类:构造器(从无到有)、 生产器(从旧到新)、观察器、变值器
①构造器:顾名思义,创建一个该类型的新对象,方法可以由参数,也可以没有参数,如构造方法。但需要注意的是,如果构造器有参数,那么参数一定不能是本类型的对象,因为构造器本身是从无到有的构造,是一个全新的构造,直接利用已有对象产生的新对象属于生产器的范畴。构造器包括构造函数和静态函数等。
②生产器:从已有的原对象中产生新的对象,如String中的concat方法
③观察器:返回对象的某些属性值,不会对其进行修改。如List中的size方法
④变值器:改变对象的属性,只有可变数据类型才拥有,如List中的add方法。变值器通常返回void,此时说明它一定改变了对象的某些内部属性;当然,变值器的返回值也可能为非void类型,此时不一定改变了对象的属性
三.设计ADT的原则
1.设计简洁、一致的操作。
通过简单操作的组合实现复杂的操作。操作的行为应该
是内聚的。
2.要足以支持client对数据所做的所有操作需要,且用操作满足client需要的难度要低。
用来判断这一标准的方法:对象每个需要被访问到的属性是否都能够被访问到
3.要么抽象、要么具体,不要混合 — 要么针对抽象设计,要么针对具体应用的设计。面向具体应用的类型不应包含通用方法,面向通用的类型不应包含面向具体应用的方法。
四.表示独立性(RI)
1.client使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不应影响外部规约和客户端。
2.之前在Part 1中介绍了规约的作用,规约隔离了client和方法,通过前提条件和后置条件充分刻画了ADT的操作,规约规定了client和开发人员之间的契约。
因此client知道可以依赖哪些内容,开发人员知道可以安全更改哪些内容。
client可以依赖的是public的成员属性以及方法,并且对于他们的了解都是满足规约前置条件的。
开发人员可以在满足规约前提下安全地更改这些属性以及方法,实现对变化的“隔离”
以下面的ADT为例:
五.不变量
不变量是程序在任何时候总是保持true的性质。由ADT来负责保持其不变量,与client端的任何行为无关。例如,不可变类中的不变性就是一个“不变量”。
这里以避免表示泄露为例介绍,避免表示泄露是ADT中非常重要的不变量。
①
六.RI和AF
1.对于实际数据,我们可以只关注两个空间上的数据:
①表示值构成的空间R:实现者看到和使用的值
②抽象值构成的空间A:client看到和使用的值
而我们的程序,实际上在做的就是从R到A建立一个映射,以供用户使用:
Section 2 OOP
一.基本概念
这里只是以几句话带过总结一下,因为这些概念经过这几周的学习,已经非常熟悉了。
1.类:是一个抽象概念与模板,属性代表了状态,方法代表了行为,所有该类对象的共有特征
2.对象:类的实例
3.静态属性和方法是属于该类整体的,通过类名直接可以调用而无需直接创建对象。静态方法无法直接调用非静态成员
二.接口和枚举类型
1.接口是和类来进行分工合作的,接口中只有方法的定义,没有实现,这些方法的具体实现需要到类中完成。(目前的新版Java中接口已经可以定义静态方法)因此接口中记录ADT的规约,类中实现ADT的具体设计
三.封装和信息隐藏
其实信息隐藏的概念在前面已经多次提及了,规约将ADT内部实现与用户所见隔离起来,这就是信息隐藏的一种。内部数据和实现细节的隐藏程度是模块化设计质量评价的最重要标准。
那么如何更好地实现信息隐藏呢br> 1.使用接口类型声明变量
2.客户端仅使用接口中定义的方法
3.客户端代码无法直接访问属性
4.恰当选取属性的可见性
四.继承和重写
1.继承分类:
可重写继承:可以重写父类中方法
严格继承:子类只能添加新方法,无法重写父类中的方法) 。一般是由于父类中方法前加上了关键字final
final的作用:final类不能被继承,final方法无法被重写,final属性引用不可变
继承无法继承父类private属性与方法
2.重写父类方法:推荐加上@Override标识,这样会自动进行静态类型检查
①重写时方法签名必须一致,包括函数名、返回值、参数,某一对象具体执行父类的方法还是被重写的子类方法在运行时确定。
②之所以运用类的继承是为了提高代码重用性:父类型中的不空函数体可以被大多数子类型来直接复用,而那些具有特殊性的子类型,再根据自己的特性对方法进行重写,这就是继承与重写的重要意义
而如果父类型中的某个函数实现体为空,意味着其所有子类型都需要这个功能,但各有差异,没有共性,在每个子类中均需要重写。
③重写时最好不要改变原方法的本意,比如本来求和的操作你重写为求差,虽然语法没错,但运行调用时还是容易隐藏bug
3.抽象类:至少含有一个抽象方法(只有定义没有实现)的类,与接口类似,只不过是接口中的方法均为抽象方法
①抽象类不能实例化(不能用new 生成对象)
②继承某个抽象类的子类在实例化时,所有父类中的抽象方法必须已经实现
③抽象类的存在就好比一般父类于子类的存在:
<1>如果某些操作是所有子类型都共有,但彼此有差别,可以在父类型中设计抽象方法,在各子类型中重写。
<2>所有子类型完全相同的操作,放在父类型中实现,子类型中无需重写。
<3>有些子类型有而其他子类型没有的操作,不要在父类型中定义和实现,而应在特定子类型中实现。
<4>抽象类中抽象方法的作用与一般父类中的空函数体作用非常类似。但是父类空函数体在被子类重写后规约可能被破坏,而抽象类中的抽象方法在被子类实现时必须还要满足规约
五.多态、子类型、重载
1.多态的三种类型:特殊多态、参数化多态、子类型多态
①特殊多态–重载:两个不同的方法拥有同样的名字,并且参数不同(如参数个数、参数类型),那么一个方法就是另一个方法的重载。这是一种静态多态,在编译阶段进行检查
<1>注意,两个方法名字必须相同,参数必须不同,返回值可以不同也可以相同
根据这两个例子我们可以总结一下:不管有没有包括重载或是重写,对于一个变量先进行编译时静态检查,这时我们要看它被声明为什么类型,它的类中有没有这个函数,参数是否对等,都满足条件才会通过检查,主要是对重载的检查;然后会是运行中检查,运行时会检查它到底是不是这个类的子类对象,如果是就会调用子类中相应方法(如果有),否则调用父类的相应方法,主要是对重写的检查
②参数多态性:泛型编程
泛型编程是一种编程风格,其中数据类型和函数是根据待指定的类型编写的,随后在需要时根据参数提的特定类型进行实例化。
泛型是一种抽象的统一数据类型,它不是具体的数据类型,使用泛型意味着具体实例化时在此处的变量可以是多种数据类型,这个方法/类对多种数据类型都是有效的。
使用方法有:泛型方法、泛型类、泛型接口等
③子类型多态
<1>B为A的子类,那么B可以完全代替A,每一个B都是A,子类型的规约不能弱化父类型的规约
<2>不同类型的对象可以统一的处理而无需区分,这就是子类型多态
<3>主要的意思就是一个对象无需区分它是属于子类还是属于父类,他可以代表多个类的实例,具体当作哪个类型的实例会在运行时确定
六.设计类的方法
1.尽可能使用不可变的类与变量,在不可变类中尽可能多地将属性设置为private与final,不提供变值器,避免表示泄露
2.如果必须使用可变类:属性也要尽可能设置为private,注意构造函数与set,get方法中的表示泄露问题,可采取防御式拷贝,变值器一定要是通过开发人员在类内提供的变值器,而不是得到属性后用户可任意修改的变值器
Section 3 等价性
一.等价关系
这里的等价关系与集合论中的等价关系是一样的,满足子凡,对称,传递的关系,不再赘述
二.ADT的等价
1.如果AF映射到同样的结果,则等价–开发者角度
2.站在外部观察者角度:对两个对象调用任何相同的操作,都会得到相同的结果,则认为这两个对象是等价的。反之亦然!
3.开发人员在程序中对外暴露出的方法应该要满足观察者角度的等价性
三.==与equals的区别
1.“==”是判断二者的引用等价性,即指向的地址空间是否是一样的,用于基本数据类型的判断。
2.equals是判断对象等价性,看二者的值是否是相等的。每个类都继承有Objects的equals方法,缺省实现也为双等 ,但是可以进行重写
3.对象数据类型应该总是使用equals进行判断相等而不是双等 ,因为如果要以地址相等作为判断依据,那么缺省实现的equals就可以完成,如果要以某些属性作为判断依据,那么重写合适的equals方法即可
四.重写equals方法
1.遵循一般重写规则,即方法签名一定要相同,参数是Object类型而不是待比较类型,否则会变为重载
2.标准重写规则:判断是否为null,是null一定返回false;之后判断指定对象是否为同一类或是子类,如果不是,也直接返回false;之后强转为该类对象(为了子类考虑),根据需要的属性进行比较
一个标准的重写方法如下:
五.可变类中的等价性
1.观察等价性:在不改变状态的情况下,两个可变对象是否看起来一致
2.行为等价性:调用对象的任何方法都展示出一致的结果
3.对可变类型来说,往往倾向于实现严格的观察等价性,但在有些时候,观察等价性可能导致bug,甚至可能破坏RI。因为当某个可变对象包含在集合类中,当其发生改变时,集合类的行为是不确定的
4.为了避免上述bug,对可变类型,实现行为等价性即可。也就是说,只有指向同样内存空间的objects,才是相等的。 所以对可变类型来说,无需重写equals和hashCode这两个函数,直接继承Object的两个方法即可。如果一定要按照观察等价性作判断,那么并不推荐重写equals和hashCode来实现,而是定义另外的新方法来判断,避免集合类不确定行为之类的bug
文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览91518 人正在系统学习中
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!