本章小结:
ADT
OOP
ADT与OOP
》》ADT (抽象数据类型)
ADT具有以下几个能表达抽象思想的词:
——抽象化:用更简单、更高级的思想省略或隐藏低级细节。
——模块化: 将系统划分为组件或模块,每个组件可以设计,实施,测试,推理和重用,与系统其余部分分开使用。
——封装:围绕模块构建墙,以便模块负责自身的内部行为,并且系统其他部分的错误不会损坏其完整性。
——信息隐藏: 从系统其余部分隐藏模块实现的细节,以便稍后可以更改这些细节,而无需更改系统的其他部分。
——关注点分离: 一个功能只是单个模块的责任,而不跨越多个模块。
考虑ADT时,它是由操作定义的,与其内部如何实现无关。
可变类型的对象:提供了可改变其内部数据的值的操作。Date
不变数据类型: 其操作不改变内部值,而是构造新的对象。String
四种抽象类型操作:
——构造器:创建某个类型的新对象,⼀个创建者可能会接受⼀个对象作为参数,但是这个对象的类型不能是它创建对象对应的类型。可能实现为构造函数或静态函数。(通常称为工厂方法)
t* -> T ,例如:Integer.valueOf( )
——生产器:通过接受同类型的对象创建新的对象。
T+ , t* -> T, 例如:String.concat( )
——观察器:获取抽象类型的对象然后返回一个不同类型的对象/值。
T+ , t* -> t, 例如:List.size( ) ;
——变值器:改变对象属性的方法 ,变值器通常返回void,若为void,则必然意味着它改变了对象的某些内部状态;当然,也可能返回非空类型
T+ , t* -> t || T || void, 例如:List.add( )
解释:T是ADT本身;t是其他类型;+ 表示这个类型可能出现一次或多次;* 表示可能出现0次或多次。
四个特性:独立性、不变量与表示泄露、抽象函数AF、表示不变量RI
——表示独立性:ADT内部表示的变化不应影响外部spec和客户端。
——不变量与表示泄露:不变量就是一直是true的变量,用来保持程序的正确性;比如用private final定义变量,而不是public
——AF:表示域R和抽象域A之间的函数关系,一种对于合法映射的解释
——RI:所有表示值的子集,包含了所有合法的表示值
在注释中写出AF和RI:
——在抽象类型(私有的)表示声明后写上对于抽象函数和表示不变量的注解,这是一个好的实践要求。我们在上面的例子中也是这么做的。
——在描述抽象函数和表示不变量的时候,注意要清晰明确:
对于RI(表示不变量),仅仅宽泛的说什么区域是合法的并不够,你还应该说明是什么使得它合法/不合法。
对于AF(抽象函数)来说,仅仅宽泛的说抽象域表示了什么并不够。抽象函数的作用是规定合法的表示值会如何被解释到抽象域。作为一个函数,我们应该清晰的知道从一个输入到一个输入是怎么对应的。
——本门课程还要求我们将表示暴露的安全性注释出来。这种注释应该说明表示的每一部分,它们为什么不会发生表示暴露,特别是处理的表示的参数输入和返回部分(这也是表示暴露发生的位置)。
——下面是一个Tweet
类的例子,它将表示不变量和抽象函数以及表示暴露的安全性注释了出来:
// Immutable type representing a tweet.
public class Tweet {
private final String author;
private final String text;
private final Date timestamp;
// Rep invariant:
// author is a Twitter username (a nonempty string of letters, digits, underscores)
// text.length <= 140
// Abstraction function:
// AF(author, text, timestamp) = a tweet posted by author, with content text,
// at time timestamp
// Safety from rep exposure:
// All fields are private;
// author and text are Strings, so are guaranteed immutable;
// timestamp is a mutable Date, so Tweet() constructor and getTimestamp()
// make defensive copies to avoid sharing the rep's Date object with clients.
// Operations (specs and method bodies omitted to save space)
public Tweet(String author, String text, Date timestamp) { ... }
public String getAuthor() { ... }
public String getText() { ... }
public Date getTimestamp() { ... }
}
》》OOP
——对象:是一个类的实例化,有状态和行为。在 java中状态就是属性域,行为就是方法
——类:描述一类对象的行为和状态。类定义了属性类型和行为实现
——接口:是抽象方法的集合,可以被类继承,里面的方法都是抽象的。除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。一个接口可以扩展其他接口,一个类可以实现多个接口。
——抽象类:抽象类除了不能实例化对象之外,成员变量、成员方法和构造方法的访问方式和普通类一样。抽象类必须被继承,才能被使用。一个类只能继承一个抽象类,而一个类却可以实现多个接口。如果一个类包含抽象方法,那么该类必须是抽象类。任何子类必须重写父类的抽象方法,或者声明自身为抽象类。构造方法,类方法(用static修饰的方法)不能声明为抽象方法。
继承与重写:
extends 继承就是子类把父类的特征和行为继承下来,就是实例方法和实例域
@Override 重写就是子类把父类中的方法重新更改内容,名字和参数以及返回值类型均不变
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。
声明为final的方法和声明为static的方法都不能重写,声明static的方法可以重新声明
重写方法访问权限不能比父类的低,比如父类public,子类不能protected
super.方法 表示子类运行父类的方法
多态与重载:
多态是同一行为具有多种不同表现形式或形态的能力,三种类型的多态
——特殊多态:功能重载
——参数多态: 泛型或泛型编程。
——子类型多态、包含多态:当一个名称表示许多不同的类与一些常见的父类相关的实例。
重载是在一个类里面,方法名字相同,而参数不同(必须不同),返回类型可以相同也可以不同。每个重载的方法(或构造函数)都必须有一个独一无二的参数类型列表。重载是静态多态,根据参数列表进行最佳匹配,在编译阶段时决定要具体执行哪个方法 (static type checking) ,与之相反,重写方法则是在run-time进行dynamic checking!
泛型:比如Set
对于泛型接口,可以有泛型的实现类,也可以有非泛型的实现类:interface Set
apple implements Set
》》ADT与OOP
很多时候需要判断对象间的等价性。
==表示引用等价性,两个变量的索引指向相同的地址。在判断基本类型时使用
equals表示我们心中希望的对象等价性,在ADT中需要我们自己定义
——抽象函数:回忆一下抽象函数(AF: R → A ),它将具体的表示数据映射到了抽象的值。如果AF(a)=AF(b),我们就说a和b相等
——等价关系:等价是指对于关系E ⊆ T x T ,它满足:
自反性:x.equals(x)必须返回true
对称性:x.equals(y)与y.equals(x)的返回值必须相等。
传递性:x.equals(y)为true,y.equals(z)也为true,那么x.equals(z)必须为true。
hashcode():equals的对象必须拥有相同的hashcode,不euqals的对象hashcode不一定不同。把抽象类型的抽象值映射为整数
可变对象的等价性:观察等价性/行为等价性
——观察等价性:在不改变状态的情况下,两个mutable对象是否看起来一致
——行为等价性:调用对象的任何方法都展示出一致的结果
在java中对于可变对象,通常equals表示的是观察等价性
注意:当可变对象作为集合的元素时要特别小心。如果对象内容改变后会影响相等比较而且对象是集合的元素,那么集合的行为是不确定的。