《OnJava8》精读(一) 一切的开始:对象

在这里插入图片描述

@

一切的开始

《On Java 8》是什么?

它是《Thinking In Java》的作者Bruce Eckel基于Java8写的新书。里面包含了对Java深入的理解及思想维度的理念。可以比作Java界的“武学秘籍”。任何Java语言的使用者,甚至是非Java使用者但是对面向对象思想有兴趣的程序员都该一读的经典书籍。目前豆瓣评分9.5,是公认的编程经典。
豆瓣评分

为什么要读书学习编程?

我认为编程的学习分为两种:

  • 实际项目开发
  • 书籍理论的学习

这两种各有优缺点。实际开发学习效率最快,但是学习面不够广,项目中遇到的问题会被深刻记忆,但是没有遇到的问题(或者被规避的问题)却不能被掌握。
相反,书籍学习可以地毯式的、条理式的学习,但是过程漫长,缺乏一定的目标的话,容易中断。
但是,书籍的学习还是有必要。基础知识的学习永远都不过时。

为什么要写这个系列的精读博文?

如上个问题提到的,由于书籍读起来时间久,过程漫长,因此产生了写本精读系列的最初想法。除此之外,最重要的一个原因是,由于中文版是译版,读起来还是有较大的生硬感(这种差异并非译者的翻译问题,类似英文无法译出唐诗),有时候这导致我们对理解作者意图需要一点推敲。再加上原书的内容很长,只第一章就多达一万多字(不含代码),读起来就需要大量时间。

所以,如果现在有一个人能替我们先仔细读一遍,筛选出其中的精华,让我们可以在地铁上或者路上不用花太多时间就可以了解这边经典书籍的思想那就最好不过了。于是这个系列诞生了。

本系列博文适合那些人读?

只要是对编程思想感兴趣的都可以。对学生和刚毕业的人来说可以起到引领作用。对已经有多年编程经验的人来说,温故知新,基础知识在本书作者这样的大佬头脑里可能爆发出不同的思想,也能使你得到一些启发。

一些建议

着重建议读本书的英文版原文。此外,也可以参考本书的中文译版。我在写这个系列的时候,会尽量的保证以“陈述句”的方式表达书的内容,我也会写出自己的部分观点,但是这种观点会保持理性并尽量少而精。本系列中对于原作的内容会以引用的方式体现。

最重要的一点,大家可以通过博客平台的评论功能多加交流,这也是学习的一个重要环节。就像作者在前言中说的那样:

与几年前我们依赖印刷媒体相比,YouTube,博客和 StackOverflow 等网站的出现让寻找答案变得简单。请结合这些学习途径和努力坚持下去。

前言及简介

本章总字数:7600

关键词:

  • 本书基于 Java 8 版本
  • 本书的最低要求需要懂得基本的语法概念,如:if、while和方法等
  • 教学目标:循序渐进、更简单的讲解、不过多研究细节
  • 本书可能会提到语言的缺陷,希望读者理性看待(怕引战)
  • Java的图形界面很失败

在前言里作者主要说明的写书的初衷以及感叹与他自己的大名鼎鼎的《Thinking In Java》写作时期Java版本的变化。

作者着重说了自己的教学目标,希望自己的书可以让读者循序渐进的、以更简单的方式学到更多东西。在简介中,作者的一句话很重要:我认为信息的重要性是分层次结构的。绝大多数情况下,我们没必要弄清问题的所有本质。

这就注定了本书的内容是尽量在不纠结语言设计细节的基础上讲解。

除此之外,作者还强调了“承认错误”的重要性。认为现在的程序员已经不再把语言当做一个工具而是一个阵营,对于提出语言设计缺陷的人进行攻击。

看过我此前作品的读者们应该清楚,我一般倾向于指出这些错误。Java 拥有一批狂热的粉丝。他们把语言当成是阵营而不是纯粹的编程工具。我写过 Java 书籍,所以他们兀自认为我自然也是这个“阵营”的一份子。当我指出 Java 的这些错误时,会造成两种影响:

早先许多错误“阵营”的人成为了牺牲品。最终,时隔多年后,大家都意识到这是个设计上的错误。然而错误已然成为 Java 历史的一部分了。

更重要的是,新手程序员并没有经历过“语言为何采用某种方式实现”的争议过程。特别是那些隐约察觉不对却依然说服自己“我必须要这么做”或“我只是没学明白”从而继续错下去的人。更糟糕的是,教授这些编程知识的老师们没能深入地去研究这里是否有设计上的错误,而是继续错误的解读。总之,通过了解语言设计上的错误,能让开发者们更好地理解和意识到错误的本质,从而更快地进步。

这段话让我想起了“XXX是世界上最好的语言”这种古老的段子。虽然大部分时候我们的这种争论只是玩笑,但是确实也在论坛圈子中见过各种各样关于语言的争吵。我刚毕业时是做C#,在很久之前关于C#和Java的对比也争执过。后来渐渐发现很多程序员容不得别人说自己所用语言的不好(无论是那种语言),这一点明显体现在工作3~5年这区间中。因为刚刚毕业的人对语言的执着还不是很深,而工作已经很久的大佬已经不在乎语言的特性而更关注于哪种语言更合适做某件事情。

所以从作者的这句话中已经能感受到,作者的格局思想超出了语言本身,而是将语言作为工具的辩证思维。既然是工具,当然就有其优缺点,这是很正常的事情。如果某一个万能语言可以完美到没有任何问题,又怎么会出现这么多编程语言?语言之所层出不穷,是因为不同的场景下适用的编程思想不同

因此,对比作者的理性思考,我们从书中应该学习他的这种辩证思维。

在简介的最后一部分,作者提到了Java的图形界面,并认为其是“设计失败的绝佳教材”。

桌面程序领域似乎从未尝勾起 Java 设计师的野心。Java 没有在图形界面取得该有的一席之地。另外,曾被大肆吹嘘的 JavaBeans 也没有获得任何影响力。(许多不幸的作者花了很多精力在 Swing 上编写书籍,甚至只用 JavaBeans 编写书籍)。Java 图形界面程序大多数情况下仅用于 IDE(集成开发环境)和一些企业内部应用程序。你可以采用 Java 开发图形界面,但这并非 Java 最擅长的领域。

作者的意思是指本书不会着重图形界面方面的知识,且说了图形界面不是Java的优势。以免想了解Java图像界面知识的人读了这本书。

第一章 对象的概念

本章总字数:14000

关键词:

  • 抽象是解决问题的基石
  • Smalltalk是第一个成功的面向对象语言并影响了后来的Java
  • “类型”与“接口”的对应关系是面向对象程序设计的基础
  • 访问控制有效的实现了应用程序员不触摸他们不应该触摸的部分代码
  • 继承不应当随处可见,而“组合”更加灵活
  • 在派生中,"is-a"和"is-like-a"有区别
  • 掌握多态可以使我们的编程顺利、高效
  • 集合和泛型的使得数据的存储、处理变得简便
  • 在Java中对象的生命周期管理更简单(自动垃圾回收)

本章是总领性的概念性章节。篇幅在全书中也算是较长的。主要阐述了面向对象的基础概念,包括:抽象、接口、类、继承等,也提到了常用的一些知识,如:集合、泛型、异常处理及垃圾回收。

在本章的开始,作者说明了本章内容是讲解OOP基础的,如果读者看不懂可以跳过以后再读。我认为作者将本章放置在所有章节的前面是很明智的。对于有一定基础的读者,本章可以作为重温,对于开始接触编程的读者,本篇也可以让人大体有一定了解。

作者提到了抽象是解决问题的基础所在。正是因为有了抽象的概念,才衍生出了对象。并提到了Smalltalk语言,Smalltalk 是一个面向对象语言,比Java早。它的一些概念很大程度影响到了后来的Java。比如对象的思想:

  • 万物皆对象。你可以将对象想象成一种特殊的变量。它存储数据,但可以在你对其“发出请求”时执行本身的操作。理论上讲,你总是可以从要解决的问题身上抽象出概念性的组件,然后在程序中将其表示为一个对象。
  • 程序是一组对象,通过消息传递来告知彼此该做什么。要请求调用一个对象的方法,你需要向该对象发送消息。
  • 每个对象都有自己的存储空间,可容纳其他对象。或者说,通过封装现有对象,可制作出新型对象。所以,尽管对象的概念非常简单,但在程序中却可达到任意高的复杂程度。
  • 每个对象都有一种类型。根据语法,每个对象都是某个“类”的一个“实例”。其中,“类”(Class)是“类型”(Type)的同义词。一个类最重要的特征就是“能将什么消息发给它?”。
  • 同一类所有对象都能接收相同的消息。这实际是别有含义的一种说法,大家不久便能理解。由于类型为“圆”(Circle)的一个对象也属于类型为“形状”(Shape)的一个对象,所以一个圆完全能接收发送给"形状”的消息。这意味着可让程序代码统一指挥“形状”,令其自动控制所有符合“形状”描述的对象,其中自然包括“圆”。这一特性称为对象的“可替换性”,是OOP最重要的概念之一。

在解释类的概念时,作者举了一个灯泡的例子:

在这里插入图片描述

Light lt = new Light();
lt.on();

在这个例子中,类型/类的名称是 Light,可向 Light 对象发出的请求包括打开 on、关闭 off、变得更明亮 brighten 或者变得更暗淡 dim。通过声明一个引用,如 lt 和 new 关键字,我们创建了一个 Light 类型的对象,再用等号将其赋给引用。

为了向对象发送消息,我们使用句点符号 . 将 lt 和消息名称 on 连接起来。可以看出,使用一些预先定义好的类时,我们在程序里采用的代码是非常简单直观的。

紧接着,作者开始解释多态的其他内容。

其中,对封装的理解很重要。作者认为,编程领域可以划分为研发和应用。专门做应用程序的程序员不应该关注基础工具部分的代码逻辑。当一个研发程序员作出一个工具类时,应该对内部的细节隐藏,而只开放需要让使用者知道的部分。这个思想引出了Java的一个概念——访问控制:

public(公开)表示任何人都可以访问和使用该元素;

private(私有)除了类本身和类内部的方法,外界无法直接访问该元素。private 是类和调用者之间的屏障。任何试图访问私有成员的行为都会报编译时错误;

protected(受保护)类似于 private,区别是子类(下一节就会引入继承的概念)可以访问 protected 的成员,但不能访问 private 成员;

default(默认)如果你不使用前面的三者,默认就是 default 访问权限。default 被称为包访问,因为该权限下的资源可以被同一包(库组件)中其他类的成员访问。

作者提到了组合和聚合的一些区别,这些理论很抽象。对比了英文原著及译文,我认为这两者更多是语义、思维上的区别。两者的代码实现很可能看不出区别。两个类生命周期不同步,则是聚合关系,生命周期同步就是组合关系。

继承是一个很重要的点。利用继承可以使问题的解决方式变为“解决这一类问题”。如书中的例子:

在这里插入图片描述

垃圾回收机对垃圾进行分类。基类是“垃圾”。每块垃圾都有重量、价值等特性,它们可以被切碎、熔化或分解。在此基础上,可以通过添加额外的特性(瓶子有颜色,钢罐有磁性)或行为(铝罐可以被压碎)派生出更具体的垃圾类型。此外,一些行为可以不同(纸张的价值取决于它的类型和状态)。使用继承,你将构建一个类型层次结构,来表示你试图解决的某种类型的问题。第二个例子是常见的“形状”例子,可能用于计算机辅助设计系统或游戏模拟。基类是“形状”,每个形状都有大小、颜色、位置等等。每个形状可以绘制、擦除、移动、着色等。由此,可以派生出(继承出)具体类型的形状——圆形、正方形、三角形等等——每个形状可以具有附加的特征和行为

在派生中,"is-a"和"is-like-a"有区别。作者引出了一个问题:继承应该只覆盖基类的方法(不应该添加基类中没有的方法)吗。

如果答案是肯定的,那派生类和基类没有什么区别,且有相同的接口。可以用一个派生类对象完全替代基类对象,被称作"替代原则"(也叫纯粹替代),也就是“is-a”关系。作者认为“is-a”关系是一种处理继承的理想方式

与之相反的,如果在继承过程中,派生类有了新的接口,与基类有了差异,那它就不算是完全替代,这就是“is-like-a”关系。

当你看到替代原则时,很容易会认为纯粹替代是唯一可行的方式,并且使用纯粹替代的设计是很好的。但有些时候,你会发现必须得在派生(扩展)类中添加新方法(提供新的接口)。只要仔细审视,你可以很明显地区分两种设计方式的使用场合。

多态的实用性很强。完全掌握多态对程序员至关重要。理解了多态的实现原理,我们就会明白,面向对象语言都使用了后期绑定的概念。

当向对象发送信息时,被调用的代码直到运行时才确定。编译器确保方法存在,并对参数和返回值执行类型检查,但是它不知道要执行的确切代码。

作者举了一个例子:

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);

在这里插入图片描述

当预期接收 Shape 的方法被传入了 Circle,会发生什么。由于 Circle 也是一种 Shape,所以 doSomething(circle) 能正确地执行。也就是说,doSomething() 能接收任意发送给 Shape 的消息。这是完全安全和合乎逻辑的事情。

单继承概念。作者对比了C++,在Java里不允许像C++那样有多个基类。

Java 的单继承结构有很多好处。由于所有对象都具有一个公共接口,因此它们最终都属于同一个基类。相反的,对于 C++ 所使用的多继承的方案则是不保证所有的对象都属于同一个基类。从向后兼容的角度看,多继承的方案更符合 C 的模型,而且受限较少。

对于完全面向对象编程,我们必须要构建自己的层次结构,以提供与其他 OOP 语言同样的便利。我们经常会使用到新的类库和不兼容的接口。为了整合它们而花费大气力(有可能还要用上多继承)以获得 C++ 样的“灵活性”值得吗?如果从零开始,Java 这样的替代方案会是更好的选择。

而像Java这样单继承会有一个最大的好处——垃圾回收机制——会更好实现。原因很简单,“由于运行期的类型信息会存在于所有对象中,所以我们永远不会遇到判断不了对象类型的情况”。

集合的概念。作者举例了ArrayList 和LinkedList。两者都是对List 的实现,但是由于底层的实现方式不一样,导致了其有不同的使用场景。LinkedList 的查找更加快,而ArrayList 的插入效率更高。此外,集合与泛型的搭配,使得数据类型的存储和处理变得方便。

最后部分,作者对比了Java与C++在对象生命周期上理念的不同。Java的方式致使垃圾回收机制成为可能,而C++则必须以编程的方式销毁对象。

Java 的内存管理是建立在垃圾收集器上的,它能自动发现对象不再被使用并释放内存。垃圾收集器的存在带来了极大的便利,它减少了我们之前必须要跟踪的问题和编写相关代码的数量。因此,垃圾收集器提供了更高级别的保险,以防止潜在的内存泄漏问题,这个问题使得许多 C++ 项目没落。

总结

前言简介及第一章都是总领性的概念部分。没有涉及语言的具体细节。大部分内容都是介绍Java的OOP特点,并对比了C++的区别。作者认为,Java中的OOP虽然看起来要稍显复杂,但是严格按照 Java 规范编写的程序会比面向过程程序更容易被理解

posted @ 2021-01-23 21:03  Hi-Jimmy  阅读(1719)  评论(0编辑  收藏  举报