
TIJ 第一章
1 对象导论
我们之所以将自然界分解,组织成各种概念,并按其含义分类,主要是因为我们是整个口语交流社会共同遵守的协定的参与者,这个协定以语言的形式固定下来……除非赞成这个协定中规定的有关语言信息的组织和分类,否则我们根本无法交谈——Benjamin Lee Whorf
1.1 抽象过程
所有语言都提供抽象机制,我们解决我呢提的复杂性直接取决于抽象的类型和质量。
Java 作为面向对象的语言具有 Smalltalk 的五个基本特性,具体如下
万物皆对象
将对象视为奇特的变量,它可以存储数据,除此之外,你还可以要求它在自身上执行操作。理论上讲,你可以抽取待求解问题的任何概念化构建(狗、建筑物、服务等),将其表示为程序中的对象
程序是对象的集合,它们通过发送消息来告知彼此所要做的
要想请求一个对象,就必须对该对象发送一条消息。更具体得说,可以把消息想象为对某个特定对象的方法的调用请求
每个对象都有自己的由其他对象所构成的存储
换句话说,可以通过创建包含现有对象的包的方式来创建新类型的对象。因此,可以在程序中构建复杂的体系,同时将其复杂性隐蔽在对象的简单性背后
每个对象都拥有其类型
按照通用的的说法,“每个对象都是某个类(class)的一个实例(instance)”,这里“类”就是类型的同义词。每个类最重要的区别于其他的特性就是“可以发送什么样的消息给它”
某一特定类型的所有对象都可以接收同样的消息
这是一句意味深长的表述,因为“圆形”类型的对象同时也是“几何形”类型的对象,所以一个“圆形“对象必定能够接收发送给”几何形“对象的消息。这意味着可以编写与”几何形“交互并自动处理所有几何形性质相关的事物的代码。这种可替代性是 OOP 中最强有力的概念之一
书中的表述有些许抽象,但是拥有 Java 基础的同学一眼就能明白,类与对象的关系,类更像是一个模板,通过这个模板我们可以创造很多很多性质相同,地址不同的对象。第五个特质在后续的学习中也会着重介绍,这就是 Java 中多态的表现,OOP(面向对象程序设计)
1.2 每个对象都有一个接口
在程序执行期间,具有不同状态而其他方面都相似的对象会被分组到对象的类中,这就是 class 关键字的由来。创建抽象数据类型(类)是面向对象程序设计的基本概念之一,区别于后面学习到的 Java 中的接口,我们进行 UML 作图
这里上半部分就是 类型名称
下半部分是 接口
决定接口的便是对象的类型
Light lt = new Light();
lt.on();
类型名称 Light
特定对象 lt
调用 打开的方法
1.3 每个对象都提供服务
当正在试图开发或理解一个程序设计时,最好的方法之一就是将对象想象为”服务提供者“。
用好理解的话来说就是,一个程序的运行,就是靠无数个对象调来调去实现的,每个对象都会提供对应的服务。人类社会就是一个巨大的程序,无数职业在为社会提供各种服务,从而社会健康运行
1.4 被隐藏的具体实现
依旧是社会的例子,我们作为程序员,并不需要直到芯片是怎么生产的,而生产芯片的也不知道我们的业务具体是怎么实现的,程序员只要买到芯片来用,生产芯片的也只享受一下程序员提供的各种软件来消磨时光。对于程序也是如此,没有关联的对象并不需要直到彼此之间的实现。书中是这样描述的:
访问控制的第一个存在原因就是让客户端程序员无法触及他们不应该触及的部分——这些部分对数据类型的内部操作来说是必需的,但这并不是用户解决特定问题所需的接口的一部分。这对客户端程序员来说其实是一项服务,因为他们可以很容易地看出哪些东西对他们来说很重要,而哪些东西可以忽略
访问控制的第二个存在的原因就是允许库设计者可以改变类内部工作方式,而不用担心会影响到客户端程序员
第一点就是我们上面举的例子,第二点就更好理解了,GPU 更新换代了,我只需要把原来的卸下来,重新装上新升级的 GPU 就能运行了。至于哪里升级了,多了多少电容这种东西,我们作为客户并不需要了解
而 Java 用三个关键字在类的内部设定边界:public, private, protected。这些访问指定词决定了紧跟其后被定义的东西可以被谁使用。public 表示紧随其后的元素对任何人都是可用的,而 private 这个关键字表示除类型创建者和类型的内部方法之外的任何人都不能访问的元素。protected 关键字和 private 作用相当,差别仅在于继承的类可以访问 protected 成员,但是不能访问 private 成员。还有一种默认访问权限,以包为界限,先按下不表,后面的章节会一一介绍
1.5 复用具体实现
一旦类被创建完,那么它就应该(在理想情况下)代表一个有用的代码元。简单来说这个类可以被复用,不会发生用一次就再也不会被访问的情况。代码复用是面向对象程序设计语言所提供的最了不起的优点之一
最简单的复用某个类的方式就是直接使用该类的一个对象,此外也可以将那个类的一个对象置于某个新的类中。我们称之为”创建一个成员对象“。新的类可以由任意数量、任意类型的其他对象以任意可以实现新的类中想要的功能的方式所组成(书里这个说法真的有点变态了,可能是翻译问题?简单来说,其实就是一个类里面有其他类,大类可以随时使用被包含的小类的功能,以此来实现大类的所需要实现的功能)。因为是在使用现有的类合成新的类,所以这种概念被称为 组合
,如果组合是动态发生的,那么也被称为 聚合
。
这张 UML 图表示组合关系(实心菱形)
1.6 继承
对象这种观念,本身就是十分方便的工具。但是任有不方便的地方:你创建了一个类后,即使另一个新类与其具有相似的功能,还是得重新创建一个类。继承解决了这个问题,我们可以以现有类为基础,复制出一个副本,通过添加和修改这个副本来创建新类。
空心三角箭头表示继承
当继承现有类型时,也就创造了新类型。这个新的类型不仅包括现有类型的所有成员(尽管 private 成员被隐藏了起来,并且不可访问),更重要的是,它复制了基类的所有接口(Java 中应该是方法,区别一下 Java 中的接口)。也就意味着导出类与基类具有相同的类型。
有两种方法可以使基类和导出类产生差异:一、直接在导出类中添加新方法。二、覆盖(重写)
用一个 UML 来看看这两种方法
圆形、长方形、三角形都继承了几何型,同时重写了绘画方法和擦除方法,因为他们的绘画方式都不太一样。而三角形还增加了翻转的方法,因为圆形和长方形是轴对称图形,翻转并没有意义,所以并不需要新增方法
1.6.1 ”是一个“与”像是一个“关系
对于继承有这样的争论:继承是否应该只重写基类的方法而不去新增方法,这就是“是一个”和“像是一个”的区别。当我们在导出类中新增了方法,基类就无法访问导出类新增的这个方法,这种替代并不完美,就是“像是一个”的关系。但是仔细审查,这两种继承都有适用的场景,下面展示 将“像是一个”转化为“是一个”的一个方法
自动调温器控制制冷系统,而空调和热力泵继承自制冷系统重写制冷方法,但是很明显制冷系统并不完美,无法将热力泵组成“是一个”组合,因此我们只需要把制冷系统换成温度控制系统即可,将 heat() 方法放入基类,让导出类去继承即可,最终实现纯粹替代
1.7 伴随多态的可互换对象
在处理类型的层次结构时,经常想象把一个对象不作为它所属的特定类型来对待,而是将其当作其基类的对象来对待。这使人们可以编写出不依赖于特定类型的代码
这里 BirdController 只处理泛化的 Bird 类型,那么调用 move 会发生什么呢?
为了解决这个问题,面向对象程序设计语言使用了后期绑定
的概念。当向对象发送消息时,被调用的代码直到运行时才能确定
Java 为了实现后期绑定
使用了一小段特殊代码来替代绝对地址调用,具体在第八章会详细说明
沿用我们上面对 Shape 的类的设计,看看什么是多态
void doSomething(Shape shape) {
shape.erase();
// ...
shape.draw();
}
-------------------------------------
Circle circle = new Circle();
Triangle triangle = new Triangle();
Line line = new Line();
doSomething(circle);
doSomething(triangle);
doSomething(line);
当 circle 被传入方法中究竟会发生什么,由于 circle 被 doSomething 看作是 shape ,所以这样做完全合乎逻辑。(把将导出类看作是它的基类的过程称为向上转型)
shape.erase();
// ...
shape.draw();
这段代码不是说 “如果你是 circle 请这样做,如果是 line 请那样做”,而是意味着 “doSomething 知道你是 Shape ,你有自己的 erase() 和 draw(),你直接去做吧,请注意自己的细节”
1.8 单根继承结构
这里直接简单说,Java 所有的类默认继承自一个叫做 Object 的类,这样保证了所有的类都具备某些功能,也有了更多的灵活性
1.9 容器
学过数据结构的你一定知道,链表、数组、哈希表这些结构,Java 的标准类库中就有这样的容器。
List(用于存储序列),Map(也被称为关联组,用来建立对象之间的关联),Set(每种对象只持有一个)以及其他诸如队列、树等更多的构件。至于他们之间的区别和用法,我们在后续的章节会有更深入的了解
1.9.1 参数化类型
在 Java SE5 出现之前,容器存储的对象都只有 Java 中通用类型:Object,由于单根继承,可以存储所有东西。但是存入是向上转型为 Object,拿出时需要向下转型成我们需要的类型,这里就会有风险,如果转成了错误的类型,就会发生异常
所以 Java 增加了参数化类型,Java 中称之为 泛型
ArrayList<Shape> shapes = new ArrayList<Shape>();
这样 shapes 中就只能存放 Shape 类型的对象了
1.10 对象的创建和生命周期
在使用对象时,最关键的问题之一就是他们生成和销毁的方式。每个对象为了生存都需要资源,尤其是内存。而 Java 为我们解决了这个问题,Java 完全采用了动态内存分配方式。每当想要创建对象时就需要使用 new 关键字,而内存的释放有专门的 垃圾回收器来进行回收,避免暗藏的内存泄露,至于垃圾回收器的工作原理,我想我应该会再写一篇文章?可能吧
1.11 异常处理:处理错误
自从编程语言问世以来,错误处理始终就是最困难的问题之一,因为设计一个好的错误处理机制很困难。
异常处理将错误处理直接置于编程语言中。异常是一种对象,在错误点被抛出,并被准们设计用来处理异常的异常处理器捕获
值得注意的是,异常处理并不是面向对象的的特征,在面向对象问世以前就已经存在了
1.12 并发编程
并发编程简单来说就是同一时刻处理多个任务,唯一需要注意的点就是资源共享,我们同时处理多个任务时,不可避免会遇到任务需要使用同一个资源,比如打印机,这时打印机就会被锁定,等待使用完毕后再被释放,这听起来很好处理,但是其中的坑也是很多的,后面并发编程的章节会详细介绍
1.13 Java 与 Internet
这一章节更多在于介绍 JavaWeb 相关的东西,有兴趣可以自己看看,以后的 Spring 相关的文章也会有所接触,不在此赘述
第一章,完