《Thinking In Java》笔记
第一章 对象导论
1.1 抽象过程
面向对象语言的5个基本特性:
- 万物皆为对象,可以抽取代求解问题的任何概念化构件
- 程序是对象的集合,它们通过发送消息来告知彼此所要做的。发送消息即对某个对象的方法的调用请求。
- 每个对象都有自己的由其他对象所构成的存储。可以创建包含现有对象的包的方式来创建新类型的对象。
- 每个对象都有其类型。每个对象都是某个类的一个实例。类与类之间不同的地方就是“可以发送什么样的消息给它”
- 某一特定类型的所有对象都可以接收同样的消息。
即:对象具有状态、行为和标识,每一个对象都有内部数据(给出了对象的状态)和方法(定义对象的行为),并且每一个对象都可以与另一个对象区分开来,因为每个对象在内存中都有一个唯一的地址。
1.2 每个对象都有一个接口
接口确定了对某一特定对象所能发出的请求,在程序中必须有满足这些请求的代码。这些代码与隐藏的数据一起构成了实现。
每个对象仅能接受特定的请求。我们向对象发出的请求是通过它的“接口”(Interface)定义的,对象的“类型”或“类”则规定了它的接口形式。1
2Light lt = new Light();
lt.on();
说明:通过声明一个名称lt来为Light对象创建了一个引用,然后用new关键字新建类型为Light的一个对象,再用等号将其赋值给lt引用。为了向对象发送一条消息,我们列出引用名(lt),再用一个句点符号(.)把它同消息名称(on)连接起来。
1.3 每个对象都提供服务
将对象看成服务的提供者,程序本身将向用户提供服务。
1.4 隐藏具体实现
要注意访问控制
- public:所有人都可以访问
- private:类内部可以访问
- 默认:类可以访问同一个包中的其他类的成员
- protected:private + 继承的类可以访问protected成员
1.5 复用具体实现
- 组合:使用现有的类对象合成新的类,组合是一种has-a的关系,如汽车拥有引擎。新类的成员对象通常被声明为private。
- 聚合:如果组合是动态发生的,那么称之为聚合
- 继承
1.6 继承
- 以现有的类为基础,复制它,然后通过添加和修改这个副本来创建新类。
- 但当父类发生变动时,子类也会反映出这些变动。
- 父类包含其所有子类所共享的特性和行为,可以创建一个父类型来表示系统中某些对象的核心概念,从父类中导出其他类型,来表示此核心可以被实现的各种不同方式。
- 例:以几何为例,几何图形Shape是父类,每一个几何图形都有尺寸、颜色、位置等属性,同时每一个几何图形都可以被绘制、擦除、移动、着色等。在此基础上,可以导出各种具体的几何图形(如圆形、正方形、三角形等),每一个子类都可以具有额外的属性(如圆形有圆心)和行为(如某些图形可以被翻转)。某些行为可能并不相同,如计算图形的面积。
- 子类和父类有相同的类型,通过继承而产生的类型等价性是理解面向对象程序设计方法内涵的重要门槛。
- 子类继承了父类所有可以被继承的属性和方法
- 使子类和父类产生差异的方法:
1.6.1 is-a vs is-like-a
- is-a:子类和父类有完全相同的接口,子类没有添加父类中没有的方法,可以用一个子类对象来完全代替一个父类对象。如可以说“一个圆形就是一个几何图形”。
- is-like-a:在子类中添加了新的接口,此时子类仍然可以替代父类,但是这种替代并不完美,因为父类无法访问新添加的方法。此时可以描述为is-like-a关系。如空调,假设房间已经具备了控制冷气设备的接口,此时空调坏了,用一个既能制冷又能制热的热力泵替换了空调,那么这个热力泵is-like-a空调,但是它可以做更多的事。需要注意的是,房间只具有控制冷气设备的接口,因此房间只能和热力泵对象的制冷部分通信。尽管新对象的接口已经被扩展了,但是现有系统对原来接口之外的其他东西一无所知。
1.7 多态对象的互换使用
在处理类型的层次结构时,常把一个对象不当作它所属的特定类型来对待,而是将其当作其父类的对象来对待,这样可以编写出不依赖于特定类型的代码。以几何图形为例,所有图形都可以被绘制、擦除和移动,不关心它们是圆形、正方形、三角形或是其他什么尚未定义的形状。这样的代码是不会受添加新类型影响的,如可以从几何图形中导出一个新的子类型“五角形”,而不需要修改处理泛化几何形状的方法。通过导出新的子类型而轻松扩展设计的能力是对改动进行封装的基本方式之一,这种能力可以极大改善我们的设计,同时降低软件维护的代价。
- 前期绑定:编译器将产生对一个具体函数名字的调用,而运行时将这个调用解析到将要被执行的代码的绝对地址。然而在OOP中,程序直到运行时才能确定代码的地址,所以当消息发送到一个泛化对象时,需要采用其他的机制。
- 后期绑定:面向对象程序设计语言使用了后期绑定的概念。当向对象发送消息时,被调用的代码直到运行时才能确定。编译器确保调用方法的存在,并对调用参数和返回值执行类型检查(无法提供此类保证的语言被称为是弱类型的),但是并不知道将被执行的确切代码。
- 为了执行后期绑定,Java使用一小段特殊的代码来替代绝对地址调用,这段代码使用在对象中存储的信息来计算方法体的地址。根据这一小段代码的内容,每一个对象都可以有不同的行为表现。
- Java中动态绑定是默认行为,不需要添加额外的关键字来实现多态。(C++默认不是动态绑定,需要使用virtual关键字来实现)
- 将子类看做是它的基类的过程称为向上转型(upcasting)。
1.7.1 抽象的父类和接口
设计程序时,我们经常都希望基类只为自己的派生类提供一个接口。也就是说,我们不想其他任何人实际创建基类的一个对象,只对向上转换成它,以便使用它们的接口。为达到这个目的,需要把那个类变成“抽象”的——使用abstract关键字。 若有人试图创建抽象类的一个对象,编译器就会阻止他们。
亦可用abstract关键字描述一个尚未实现的方法——作为一个“根”使用,指 出:“这是适用于从这个类继承的所有类型的一个接口函数,但目前尚没有对它进行任何形式的实现。”抽象方法也许只能在一个抽象类里创建。继承了一个类后,那个方法就必须实现,否则继承的类也会变成“抽象”类。通过创建一个抽象方法,我们可以将一个方法置入接口中,不必再为那个方法提供可能毫无意义的主体代码。
interface(接口)关键字将抽象类的概念更延伸了一步,它完全禁止了所有的函数定义。“接口”是一种相当有效和常用的工具。另外如果自己愿意,亦可将多个接口都合并到一起(不能从多个普通class或 abstract class中继承)。Java8中引入了接口的默认方法
1.8 单根继承结构
在Java中(包括除C++意外的所有OOP语言),所有的类最终都继承自单一的父类,该终极父类即Object。
单根继承结构使垃圾回收器的实现变得容易很多,因为所有对象都保证具有其类型信息,不会因无法确定对象的类型而陷入僵局,这对系统级操作(如异常处理)显得尤其重要。
1.9 容器
- C++:标准模板类库,Standard Template Library(STL)
- Object Pascal:可视化构件库,Visual Component Library(VCL)
- Smalltalk:也有提供一个非常完备的荣契机
Java的容器库体现了面向对象的设计思想:
- 不同容器提供了不同类型的接口和外部行为:栈、队列、List、Map、Tree具有不同的接口和行为
- 不同的容器对于某些操作具有不同的实现,具有不同的效率。如ArrayList和LinkedList,ArrayList可以快速的随机访问元素(花费固定时间),但是LinkedList随机访问元素需要在列表中移动,代价较高,越靠近尾部的元素花费时间越长。可对于插入元素的场景,LinkedList的开销比ArrayList要小。
1.9.1 参数化类型
在Java SE5出现之前,容器存储的对象都只具有Java中的通用类型Object。当将对象引用存入容器时,它被向上转型为Object,丢失了身份。当从容器中取出它时,得到了一个对Object对象的引用,而不是对置入时的那个类型的对象的引用。
为了将取出的Object类型转变成原先存入时的类型,使用了向下转型。
向上转型是安全的,向下转型有一定风险。如果向下转型为错误的类型,会得到称为异常的运行时错误。
在Java SE5中增加了参数化类型,在Java中它称为范型:一对尖括号,中间包含类型信息。1
2
3
4List<Shape> shapeList = new ArrayList<Shape>();
// 在新的Java版本中,后一个Shape也可以省去了,引入了类型推导机制
List<Shape> shapeList = new ArrayList<>();
1.10 对象的创建和生命期
- Java采用了动态内存分配方式,使用new关键字来构建此对象的动态实例。J
- Java的对象存在于称为“堆(heap)”的内存池中,直到运行时才知道需要多少对象,它们的生命周期如何,以及它们的具体类型是什么。
- Java提供了垃圾回收器的机制,它可以自动发现对象何时不再被使用,继而销毁它,自动释放对象所占用的内存。
1.11 异常处理
- 异常是一种对象,它从出错地点被“抛出”,并被专门设计用来处理特定类型错误的相应异常处理器“捕获”。异常处理就像是与程序正常执行路径并行的、在错误发生时执行的另一条路径。由于它是另一条完全分离的执行路径,因此它不会干扰正常的执行代码。
- 异常不能被忽略,一定会在某处得到处理。
- 异常提供了一种从错误状况进行可靠恢复的途径,可以对异常进行校正,恢复程序的执行。
- Java内置了异常处理,强制你必须使用它。
- 异常处理不是面向对象的特征,异常处理在面向对象语言出现之前就已存在。
1.12 并发编程
最初,程序员用所掌握的有关机器底层的知识来编写中断服务程序,主进程的挂起是通过硬件中断来触发的。但是这种方式难度大,且不能移植。
对于大量场景,我们只是想把问题切分成多个可独立运行的部分(任务),从而提高程序的响应能力。在程序中,这些彼此独立运行的部分被称作线程,上述概念被称为“并发”。
区分并发与并行
隐患:共享资源