软构期末复习4-8章(根据老师复习提纲整理)

软构期末复习4-8章

1.基本数据类型和对象数据类型

数据类型分为两类:一类是基本数据类型,如int,char,float等;另一类是对象数据类型,如String和各种类。

基本数据类型有对应的封装类,用于对象的引用。一般情况下二者可以自动转换,但会降低性能,应尽量避免使用

2.静态/动态类型检查

静态类型语言:所有变量的类型在编译时已知,编译器可以推导表达式类型,可以在编译阶段进行类型检查

动态类型语言:变量类型在编译时未知或不需要知道,在运行阶段进行类型检查

一种语言可以提供3种“名义上”的检查:

  • 静态检查:在编译之前进行检查,包括:语法错误、类名/函数名错误、参数数量错误、参数类型错误、返回值类型错误等。针对动态类型的语言会检查除类型以外的其他语法错误,主要关于“类型”的检查(如a是int,b是int,则得出a/b也是int)
  • 动态检查:在运行过程中进行检查,检查非法的参数值,非法的返回值、越界、空指针等。主要是关于“值”的检查(接上例,如a = 1,b = 0,执行时发现除0错误)
  • 无检查:不进行检查

三种检查的好坏程度:静态检查>动态检查>无检查

除此之外,有些问题静态检查和动态检查都无法检测出来,如整数除法(截断整数)、整数溢出、浮点数的特殊类型:

NaN,POSITIVE_INFINITY, NEGATIVE_INFINITY等

引用一个很好的例子说明:

3.可变与不可变数据类型

3.1 什么是不可变

当数据类型的对应变量的值发生了改变,那么它对应的内存地址也会发生改变,对于这种数据类型,就称不可变数据类型。如String。

每个String的变量只能指向一个不可变的对象,如果需要将对象进行改变,就需要将变量指向新的改变之后的对象,最终产生两块内存空间,分别用来存储新旧两个对象。

这种数据类型一旦被创建,其值不能改变;引用类型一旦确定其指向的对象,不能再给变其指向其他对象。

Java中使用关键字“final”来标记不可变数据类型,有以下三种形式:

  • final类无法派生自己的子类
  • final变量无法改变值/引用
  • final方法无法被子类重写

final

  • 如果final后面跟的数据类型本身是不可变数据类型,则变量在赋值之后,就不能再进行修改
  • 如果final后面跟的数据类型本身是可变数据类型,则表示final的引用不可变,即final不可指向新的对象,而对象内部的值仍然是可变的,如list仍然可以使用mutator

3.2 什么是可变

当数据类型的对应变量的值发生了改变,而它对应的内存地址不发生改变,对于这种数据类型,就称可变数据类型。如StringBuilder。

每个StringBuilder的变量可以指向一个可变的对象,如果需要将对象进行改变,则直接在当前变量指向的内存空间进行修改即可,最终还是只有一块内存空间。

对于可变对象,它可以拥有变值器(mutator),对自己的引用/值进行修改而不改变内存位置。

3.3 可变的要求

如果需要多次改变对象的值,则可以使用可变数据类型,来获得性能上的优化,但是为了安全起见,在最后返回给用户端时最好将返回不可变数据类型或者进行防御式拷贝。

3.4 不可变的要求

尽量使用final变量作为方法的输入参数,作为局部变量,作为返回值,这样可以最大限度保证安全。

3.5 二者的比较

当只有一个引用指向该对象时,二者没有区别;当有多个引用的时候,有差异

优缺点:

  • 使用不可变类型,对齐频繁修改会产生大量的临时拷贝,性能较差,但更“安全”,在其他质量指标上表现更好;
  • 可变数据类型最小化拷贝,可以提高效率,性能更高,也适合在多个模块之间共享数据。但可变性使得难以理解程序正在做什么,更难满足方法的规约。

所以,折中,具体情况具体分析。

3.6 为什么更倾向选择不可变数据类型?

因为更安全。不可变类型更安全,不会出现错误,更容易理解,并且更易于更改。可变性让你更难理解你的程序在做什么,也更难执行规约。

如果将一个可变对象传递给客户端或者其他函数,则会导致该对象的指针可以在外部或者其他函数中随意修改其指向的值,这有可能导致软件出错,而且这种错误很难被察觉和修改。

因此,最好选择不可变对象传递给其他函数或者客户端。

3.7 如何改进

  1. 进行防御式拷贝(但由于大部分时候该拷贝不会被修改,可能造成大量的内存浪费)
  2. 使用不可变类型,防止被修改,此时也不需要防御式拷贝

防御式拷贝的两种常见使用情况:

  1. 构造方法中,将内部的表示与外部的变量建立关系,如果变量是可变的,则需要进行防御式拷贝
  2. 返回时,如果内部表示是可变的,如果直接返回回去,则使内部的表示和外部的变量建立了引用,因此也需要防御式拷贝

防御式拷贝的具体实现:创建副本进行传递或者强转为对应的不可变数据类型

防御性拷贝的关键就在于不把原本类中的对象提供给调用者,而是创建一个跟封装的类中相同的对象返回给调用者,这样,你对这个参数进行修改的时候跟封装类内部的相关参数无关,也就不会改变类中的参数。这就是防御性拷贝。

4 快照图

我们使用快照图表示程序在运行时的内部状态——其堆栈(正在进行的方法及其局部变量)和堆(当前存在的对象),来理解程序现在的运行状况。

会结合代码一起考

基本数据类型:

  • 基本数据类型由裸常量表示,传入箭头是对变量或对象字段中的值的引用。

对象数据类型:

  • 一个对象数据类型是按其类型标记的圆,在里面写下字段名,用箭头指向它们的值。要了解更多详细信息,字段可以包括其声明的类型。

  • 不可变对象用双线椭圆表示,区别于可变对象

  • 可变和不可变的引用:后者用双线箭头表示

5 规约

一个完整的方法应该包含方法的规约和实现体两部分

  • 规约中应包含方法描述、方法签名、参数、返回值等信息
  • 实现体中包含具体代码

5.1 前置条件

参数:参数类型,对用户输入的要求

5.2 后置条件

  1. 返回值:返回值的含义
  2. 异常:如果有的话,说明异常是什么样的以及什么情况下会抛出异常

5.3 行为等价性

行为等价性要站在规约的角度看,看其是否实现了规约要求的功能,如果两个方法实现了相同的效果,则视为行为等价的。

5.4 规约的强度

这样的规约是更强的:前置条件更少,后置条件更多(要的更少,给的更多)

比较方法:

  1. 如果前置条件变弱,后置条件不变,则视为规约变强
  2. 如果前置条件不变,后置条件变强,则也视为规约变强
  3. 如果前置条件变弱,后置条件也变弱,则无法比较

如果规约 S2的强度强于S1,那么就可以用S2代替S1。

越强的规约,意味着implementor的自由度和责任越重,而client的责任越轻

6 ADT 操作的四种类型

  • creator:(从无到有)创建该类型的新对象,如new或静态工厂方法、String.valueOf(Object Obj)等
  • producer:(从有到新)从该类型的旧对象创建新对象,如String的concat()方法
  • observer:获取抽象类型的对象并返回不同类型的对象,如.size()
  • mutator:只有可变数据类型才有,是改变对象属性的方法,如list .add()、StringBuilder .append()

eg:

list中的四种操作:

  • creator: arraylist、linkedlist、Collections.singletonList
  • producer: Collections.unmodifiableList
  • observer: size、get
  • mutator: add , remove , addAll , Collections.sort

7 表示独立性(rep)、表示泄漏

7.1 表示独立性

表示独立性:client使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不应影响外部spec和客户端,程序员具有自己的自由。

7.2 表示泄漏

用户以某种方式可以看到内部结构,或者用户的一些操作会影响到内部表示。内部的成员变量、操作都不可以让用户看到。

表示泄漏不仅影响不变量,也影响了表示独立性:无法在不影响客户端的情况下改变其内部表示

如何防止表示泄漏:

  1. 将public变为private(防止外部修改)
  2. 使用final关键字(防止内部修改)
  3. 在规约中强调说明(不是个好的方案)
  4. 最好的方法是使用不可变类型,彻底避免表示泄漏

7.3 不变量(Rep)和表示不变量(RI)

  • 不变量是程序的一种属性,与客户端操作无关,对于程序的每个可能的运行时状态,它都始终为真。不变量包括表示不变量和避免表示泄漏。
  • 表示不变量是一个重要的ADT不变量,判断某个具体的表示是否是合法(规约)的;也可以看作所有表示值的一个子集,包含了所有合法的表示值;也可看作一个条件,描述了什么是”合法“的表示值

7.4 表示空间、抽象空间、AF

表示空间:以R表示,是开发者看到和使用的值

抽象空间:以A表示,是用户看到和使用到的值

两个空间是满射关系(每个抽象值都被某个表示值映射到,即用户的可选项一定在开发者的适用范围内),但未必是单射,也未必双射

抽象函数(AF):R和A之间的关系,即如何将R中的每一个值解释为A中的每一个值,在图中用箭头表示

什么决定了AF和RI?

同一个ADT,可以有多种表示;不同的内部表示,需要设计不同的AF和RI

同样的表示空间R,可以有不同的RI

即使是同样的R、同样的RI,也可能有不同的AF,即“解释不同”(选择)

7.5 以注释的形式撰写AF、RI

AF和RI应该写到注释中,不能写到JavaDoc中,即,不可以将具体实现泄漏给用户,用户不能知道具体实现方法,也不能对某个具体类的成员变量(属性、字段)或者实现(方法)进行修改

对于RI来说,像“所有字段都有效”这样的泛型语句是不够的,RI的工作是精确解释字段值有效与否的原因。

AF 提供“表示一组字符”这样的通用解释是不够的,要精确记录AF:如何解释每一个R值

8 接口、抽象类、具体类

8.1 接口

Java传统接口只有方法定义而没有实现,它的实现是实现该接口的类,如果类在其implements子句中声明接口,并为接口的所有方法提供方法体,则类将实现接口

  • 一个接口可以扩展一个或多个其他接口(接口之间可以继承和扩展)
  • 一个类可以实现一个或多个接口(一个接口可以有多个实现类)
  • 类可以实现接口ADT,也可以不依赖接口直接自己定义并实现ADT
  • 实际中,更倾向于用接口来定义变量,用类来实现接口

但是Java 8之后,接口可以有实现:

  1. 使用静态方法,如string .value Of()函数
  2. 使用default 关键字声明,在接口中统一实现某些功能,无需在各个类中重复实现它

eg:

8.2 抽象类

至少包含一个抽象方法的类称为抽象类,抽象类只有定义没有实现,不能实例化。抽象类中允许有具体方法,但是至少得有一个抽象方法。

继承某个抽象类的子类在实例化时,所有父类中的抽象方法必须已经实现。

注意:

  • Java支持单继承(子父类)、多实现(接口和实现类)
  • 接口之间可以继承
  • 一个类可以继承一个父类,实现多个接口

9 继承、override

方法重写是一种功能,它允许子类或子类提供已由其超类或父类之一提供的方法的特定实现,但是重写的时候不能改变方法的本意。

如果子类想要覆盖重写父类中的函数,则要和父类保持相同的方法签名。返回值可以相同,如果不相同,则子类方法中的返回值一定是父类方法中返回值的子类型。

当子类包含重写超类方法的方法时,它还可以使用关键字super调用超类方法

10 overload、多态

10.1 overload

overload即重载,一个方法有多个重名的实现,参数列表不同或返回值不同

除了名字相同之外,各个方法之间没有任何关系

重载使用静态类型检查传递的参数或者返回类型,在编译阶段决定具体执行哪个方法(重写是在运行时进行动态检查的)

重写vs重载

  • 重写时父类和子类中的方法具有相同的签名
  • 签名不同时则为重载
  • 子类重载了父类的方法后,子类仍然继承了被重写的方法

10.2 多态

多态的3种形态:

  • 特殊多态(重载、overload)
  • 参数化多态(泛型)
  • 子类型多态、包含多态(子父类继承)

11 泛型

使用“<>”,帮助声明泛型变量,如:

 public interface List<E>
 public class Entry<KeyType,ValueType>
List<Integer> ints = new ArrayList<Integer>();

使用泛型变量的三种形式 :泛型类、泛型接口和泛型方法

  • 类中如果声明了一个或多个泛型变量,则为泛型类
  • 如果接口声明了一个或多个类型变量,那么它就是泛型的

泛型不是协变的,是不变的。list<object>arraylist<object>存在父子关系,而list<integer>list<object>不存在父子关系。如果想存在父子关系,则需要加上限定符extend

考点:判断出是否为子类型关系、掌握定义一个泛型类等基本操作即可

12 equals和“==”

equals方法是Object类的方法,其代码就是使用“==”进行地址的比较,但在某些类如String中进行了重写。

基本数据类型的比较直接使用“==”即可,对象数据类型才使用equals方法。基本数据类型和其包装类型比较时则将包装自动拆开,只进行值的比较。

对于字符串来说,使用“==”比较字符串的地址是否相同;使用equals方法比较字符串内容是否相同。

总结:如果是对象数据类型,则使用equals方法。如果想判断两个对象地址是否相等,则不需重写equals方法;如果系那个判断其成员变量如name、age等是否相等,则需要重写equals方法,但重写时一定要满足自反、对称、传递原则。

如果涉及到身份识别(一般使用hashmap或者hashset存储key信息),则必须重写equals和hashCode方法。程序会首先使用hashcode来判定两个对象,如果两个hashcode相等,才会调用equals进行判断;如果hashcode不等,则不管equals是否相等,直接返回不相等。

13 判断等价性

  • 观察等价性:在不改变状态的情况下,两个对象是否看起来一致。
  • 行为等价性:调用对象的任何方法都展示出一致的结果。

对于不可变对象,二者是等价的,因为不可变对象没有mutator方法,没有办法改变对象的状态。
对可变对象来说,倾向于实现严格的观察等价性,Java对其大部分可变数据类型使用观察等价性,如Collections,部分可变类型用行为等价性,如StringBuilder。
结论:

  • 针对可变数据类型,为了保证行为等价性是一致的,就要用“==”来判断,只有指向同一个地址空间的两个变量,才认为是等价的,因为指向同一个地址空间,从一个变量处改变,另一个也跟着变;equals不需要重写
  • 针对不可变数据类型,一般会复制很多份,这时判断是否相等就需要重写equals方法,确定其行为逻辑,如下棋中的黑白两子,判断两个黑子是否相等的逻辑为:都是黑色、大小相同等;同时要重写hashcode(生成hashcode的字段要和equals中的字段一致,不能有多余的)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值