软件构造知识点复习整理(3章)

Java的基本数据类型和对象数据类型:


Object类是所有类的基类,所有类默认都继承了Object类。

基本数据类型的包装类:Boolean,Integer,Short,Long,Character,Float,Double

▪ 静态类型检查、动态类型检查

Java是静态类型的语言,在编译时需要知道所有变量的类型,编译器可以推断所有表达式的类型。而在像Python这样的动态类型语言中,这种检查会延迟到程序运行时。

静态检查和动态检查

语言可以提供的三种自动检查:

    静态检查:在程序运行之前自动发现错误。

    动态检查:执行代码时会自动发现错误。

    不检查:语言根本无法帮助您找到错误。 你必须自己观察,或者得到错误的答案。

毋庸置疑,静态捕获错误比动态捕获它更好,动态捕获它比完全捕获它更好。

静态检查意味着在编译时检查错误。

▪错误的名称,如Math.sine(2)。 (正确的名字是sin)

▪参数数量错误,如Math.sin(30,20)。

▪错误的参数类型,如Math.sin(“30”)。

▪错误的返回值类型,如返回“30”(字符串); 从声明为返回int的函数。

动态检查的内容:

非法的参数

无法表示的返回值

超出范围的索引值

调用空对象的方法

静态检查针对类型,与变量特定值无关的错误,动态检查针对由特定值引起的错误。

▪ Mutable/Immutable

不变性是一种主要的设计原则

不变数据类型创建完后,值不可修改

不变引用,一旦指定引用位置,不可再次指定

Final关键字对不可变引用提供了静态检查的支持

如final int n = 5;

则n的值不能再被改变。

    final的类声明意味着它不能被继承。

    final的变量意味着它始终包含相同的值,不能更改;

    final的方法意味着它不能被子类覆盖/重写;

对象是不可变的:一旦创建,它们总是表示相同的值。

对象是可变的:它们具有改变对象值的方法。

可变类型的优点

良好的性能

方便共享

可变性的风险

不可变类型的bug更少,更容易理解,灵活性更高。

传递可变对象容易产生潜在错误,且难以跟踪。

只在局部使用或者只有一个引用时,可变类型的使用是安全的。

对可变类型的多个引用(别名)是带来风险的原因

▪ 值的改变、引用的改变

指向可变值的不可变引用(引用不可变,值可变)

指向不可变值的可变引用(值不可变,引用可变)

▪ 防御式拷贝

返回变量是返回其一份拷贝而不是其本身。

▪ Snapshot diagram

快照图

为了理解微妙的问题,我们可以在运行时绘制正在发生的事情的图片。

快照图表示程序在运行时的内部状态 - 其堆栈(正在进行的方法及其局部变量)及其堆(当前存在的对象)。

快照图为我们提供了一种可视化更改变量和更改值之间区别的方法:

    当分配给变量或字段时,您正在更改变量箭头指向的位置。 您可以将其指向不同的值。

    当分配可变值的内容(例如数组或列表)时,您正在更改该值内的引用。

快照图中的原始值和对象值

▪原始值

    原始值由裸常数表示。 传入箭头是对变量或对象字段中的值的引用。

    ▪对象值

    对象值是由其类型标记的圆圈。 当我们想要显示更多细节时,我们在其中写入字段名称,箭头指向它们的值。

    不可变对象在快照图中用双边框表示。

▪ Specification、前置/后置条件

规格说明

规格说明是团队开发的关键,是分配责任的基础。

规格说明是实现者和使用者之间的一种契约,实现者有责任满足契约,使用者可以信赖契约。

准确的规格说明利于确定错误的位置和责任;客户端不需要阅读代码,通过说明了解程序。

规格说明给了实现者实现的自由,在保证约定下,可以自由修改实现。

通过在说明中增加对输入的限定,省略掉耗时的正确性检查工作,提升效率。(保证输入正确性的责任由调用者承担)

方法规范包含以下几个子句:

    前提条件,由关键字表示

    后置条件,由关键字效果指示

    例外行为:违反前提条件时的行为

前提条件是客户(即方法的调用者)的义务(义务)。 这是调用该方法的状态。

后置条件是该方法实施者的义务。 如果前提条件适用于调用状态,则该方法必须遵循后置条件,返回适当的值,抛出指定的异常,修改或不修改对象等。

规格说明蕴含了以下逻辑:如果前置条件满足了,则后置条件必须满足。

反之,如果前置条件不满足,后置条件则无需满足。

▪ 行为等价性

确定行为的等价,关键是一个规格说明实现是否可以替换另一个.

等价的判定由调用者视角确定。

判定可替代与否,需要对调用端依赖内容的准确描述。

Spec不应涉及实现的内部变量和私有域。

规约的强度

Deterministic(确定性):当呈现满足前提条件的状态时,结果完全确定

Under-deterministic(低确定的): 允许同一输入存在多个有效输出。

Nondeterministic(非确定的):输出结果不确定。

underdetermined (欠定的):不是确定性的spec。

规格S2强于或等于规格S1,如果

    S2的先决条件是弱于或等于S1

    对于满足S1前提条件的状态,S2的后置条件强于或等于S1。

    然后,满足S2的实现也可用于满足S1,并且在程序中用S2替换S1是安全的。

    (要求的更少,承诺的更多)

Diagramming specifications

每个点代表一个实现。

一个规格说明定义了一个区域,其中包含的点为其实现。


一个实现或者满足规格说明(区域内),或者不满足(区域外)。


加强后置条件,意味着对输出要求更多,实现自由变少。

弱化前置条件,实现中需要处理更多的情况。

上述两点使可满足的实现变少(点变少),故规格说明越强,区域越小。


▪ ADT操作的四种类型

Creators:产生类型的新对象

Producers:在已有对象基础上产生新的对象

Observers:输入抽象类型的对象,返回其他类型的对象

Mutators: 改变对象




▪ 表示独立性

好的ADT应该是表示独立的。

    这意味着抽象类型的使用与其表示(用于实现它的实际数据结构或数据字段)无关,因此表示的更改对抽象类型本身之外的代码没有影响。

    只有当我们通过前置条件和后置条件充分明确了ADT的操作,使调用者知道可以依赖哪些内容,实现者知道可以安全更改哪些内容,此时才可以修改内部表示。

仅依赖其public方法,而不依赖其private字段,在不检查和更改所有调用者代码的情况下进行更改。

▪ 表示泄露

rep暴露意味着类外的代码可以直接修改rep。

对表示泄露安全相关的参数,特别是输入参数和返回值,给出保证不泄露内部表示的策略。

▪ 不变量、表示不变量RI

好的ADT最重要属性是保持其不变性。

Rep Invariant

将rep值映射到布尔值的rep不变量:

RI:R→布尔值

▪对于rep值r,当且仅当r由AF映射时,RI(r)为真。

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

R:表示值空间(rep值)——实现时用到的值空间

A:抽象空间——需要支持的值空间

Abstraction Function 抽象函数

▪将rep值映射到abstractvalues的抽象函数

他们代表:

AF:R→A


图中的弧显示抽象函数。AF是满射的,未必单射,未必双射。

▪ 接口、抽象类、具体类

接口

Java的接口是一种用于设计和表达ADT的有用语言机制,其实现是实现该接口的类。

    Java中的接口是方法签名列表,但没有方法体。

    如果一个类在其implements子句中声明了接口,则它实现一个接口,并为所有接口的方法提供方法体。

接口和实现

▪API的多个实现可以共存

    多个类可以实现相同的API

    他们可以在性能和行为上有所不同,

    ▪在Java中,API由接口或类指定

    Interface仅提供API

    接口定义但不实现API

    Class提供API和实现

    Class可以实现多个接口

接口的优点

调用者理解ADT时只需要理解接口即可。

调用者不能在ADT的表示上创建无意的依赖。

不同实现可以在不同的类中。

抽象数据类型的多个不同表示可以共存于同一个程序中,作为实现接口的不同类。

抽象方法和抽象类

抽象方法:

    具有签名但没有实现的方法(也称为Cabstract操作)只有定义没有实现

    由关键字abstract定义

    抽象类:

    包含至少一个抽象方法的类称为抽象类

    接口:一个只有抽象方法的抽象类

    接口主要用于系统或子系统的规范。 实现由子类或其他机制提供。

具体类→抽象类→接口

▪ 继承、override

继承用于代码重用

Overriding (覆盖/重写)

子类可以重新实现父类中的方法。

    子类中的实现通过提供与父类中的方法具有相同名称,相同参数或签名以及相同返回类型的方法来覆盖(替换)超类中的实现。

    执行的方法版本将由用于调用它的对象确定。

    根据调用时的对象类型决定被调用方法的版本

    默认父类中的方法均是可被重写的

    子类只能向超类添加新方法,它不能覆盖它们

    如果某个方法无法在Java程序中覆盖,则必须以关键字final作为前缀

    子类中可以通过super关键字调用父类中被重写的方法

如果要覆盖方法:

签名保持一致

使用@Override注释,compiler 会检查覆盖方法和被覆盖的方法签名是否完全一致

复制粘贴声明(或让IDE为你做)

可见性可以保持不变或增加,但不会减少。

▪ 多态、overload

多态性是指为不同类型的实体提供一个接口,或者使用一个符号来表示多个不同的类型。

一个函数可以有多个同名的实现(方法重载)

一个类型名字可以代表多个类型(泛型编程)

一个变量名字可以代表多个类的实例(子类型)

Overloading 重载

重载是指在一个类中存在多个同名函数,必须具有不同的参数列表,返回值类型可同可不同。

重载是一种静态多态

通过参数列表决定依赖哪个实现。

调用重载函数时进行静态检查,在编译时决定调用哪个方法。

重载规则

函数重载中的规则:重载函数必须有参数数量或数据类型的不同

    重载方法必须改变参数列表。

    重载方法可以更改返回类型。

    重载方法可以更改访问修饰符。

    重载方法可以声明新的或更广泛的检查异常。

    方法可以在同一个类或子类中重载。

对某个方法的重载可以在一个类中进行,也可以在子类中进行。此时需要注意同重写的区别:如果父类和子类中两个方法的签名相同,则为重写;名称一样,参数列表不一样时,则为重载。

重写时父类和子类中的方法具有相同的签名,签名不同时则为重载。

子类重载了父类的方法后,子类仍然继承了被重载的方法。、

▪ 泛型

Generics (泛型):参数多态性——方法针对多种类型时具有同样的行为,此时可使用统一的类型表达多种类型。

泛型编程是一种编程风格,其中数据类型和函数是根据待指定的类型编写的,随后在需要时根据参数提供的特定类型进行实例化。

泛型编程围绕“从具体进行抽象”的思想,将采用不同数据表示的算法进行抽象,得到泛型化的算法,可以得到复用性、通用性更强的软件。

Java中的泛型

类型变量(type type variable)是非限定(无限制的)标识符。

使用<>,占位符类型(占位符),菱形运算符,以帮助声明类型变量。

类型变量(type type variable)是非限定(无限制的)标识符。

    它们由泛型类声明,泛型接口声明,泛型方法声明引入

    类中如果声明了一个或多个泛型变量,则为泛型类

    这些类型变量称为类的类型参数

    它定义了一个或多个充当参数的类型变量。

    泛型类声明定义了一组参数化类型,每个类型参数部分都可以调用一个。

    所有这些参数化类型在运行时共享同一个类。

如果接口声明了一个或多个类型变量,则接口是泛型的。

    这些类型变量称为接口的类型参数。

    它定义了一个或多个充当参数的类型变量。

    通用接口声明定义了一组类型,每种类型都可以调用类型参数部分。

    所有参数化类型在运行时共享相同的接口。

▪ 等价性equals()和= =

Java有两种不同的操作来测试相等性,具有不同的语义。

==运算符比较引用。

    equals()操作比较对象内容 - 换句话说,对象相等。

对于原始数据类型,必须使用==

对于对象引用类型,定义新数据类型时,需要考虑等价的含义,然后实现equals()方法。

▪ equals()的自反、传递、对称性

equals方法实现了等价关系:

    自反性:对于任何非空引用值x,x.equals(x)必须返回true。自反性

    对称性:对于任何非空引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。

    传递性:对于任何非空引用值x,y,z,如果x.equals(y)返回true且y.equals(z)返回true,则x.equals(z)必须返回true。

    一致性:对于任何非空引用值x和y,x.equals(y)的多次调用始终返回true或始终返回false,前提是未修改对象的equals比较中使用的信息。

    空值处理:对于任何非空引用值x,x.equals(null)必须返回false。

equals是所有对象的全局等价关系(对所有对象都生效)

▪ hashCode()

哈希表实现了键-值之间的映射。

它包含一个数组,键值对中的key被映射为hashcode,对应到数组的index, hashcode决定了数据被存储到数组的那个位置。

hashcode设计时会尽量确保均匀分布到数组,当多个key散列到同一个index时(冲突),哈希表维护一个列表来记录这些键值对(bucket)


只要比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode()方法必须始终返回相同整数。

两个对象equals操作相等,则hashcode必须相等。因此,重写equals时,必须重写hashCode。

如果equals比较相等,则要求hashcode相等;如果equals不等,则hashcode是否相等均可,但最好不等(提升性能)

▪ 可变对象的观察等价性、行为等价性

观察等价:在不改变对象状态的情况下(不使用mutator),无法区分对象

行为等价:改变一个对象而不改变另外一个时,仍然无法区分对象

对于不可变对象,观察和行为相等是相同的,因为没有任何mutator方法。

对于可变对象,使用观察等价性貌似可行,但是会带来潜在的问题。

▪Java使用大多数可变数据类型(如集合)的观察相等,但其他可变类(如StringBuilder)使用行为相等。

▪如果两个不同的List对象包含相同的元素序列,则equals()报告它们是相等的。

▪但是使用观察性相等会导致细微的错误,实际上我们可以轻松地破坏其他集合数据结构的rep不变量。

Equals()应该实现行为等价

两个引用相等意味着它们指向了同一个对象

可变类型应该继承Object类的equals()和hashCode()

对于不可变类型:

    equals()应该比较抽象值。 这与说equals()应该提供行为平等是一样的。

    hashCode()应该将抽象值映射到整数。

    对于可变类型:

    equals()应该比较引用,就像==。 同样,这与说equals()应该提供行为平等是一样的。

    hashCode()应该将引用映射为整数。

    可变类型不应该重写equals()和hashCode(),采用Object默认实现的即可。

你可能感兴趣的:(软件构造知识点复习整理(3章))