第一章 对象的概念,笔记

1. 面向对象五大基本特征java

(1)万物皆对象。你能够将对象想象成一种特殊的变量。它存储数据,但能够在你对其“发出请求”时执行自己的操做。理论上讲,你老是能够从要解决的问题身上抽象出概念性的组件,而后在程序中将其表示为一个对象git

(2)程序是一组对象,经过消息传递来告知彼此该作什么。要请求调用一个对象的方法,你须要向该对象发送消息程序员

(3)每一个对象都有本身的存储空间,可容纳其余对象。或者说,经过封装现有对象,可制做出新型对象。因此,尽管对象的概念很是简单,但在程序中却可达到任意高的复杂程度。github

(4)每一个对象都有一种类型。根据语法,每一个对象都是某个“类”的一个“实例”。其中,“类”(Class)是“类型”(Type)的同义词。一个类最重要的特征就是“能将什么消息发给它?编程

(5)同一类全部对象都能接收相同的消息。这实际是别有含义的一种说法,你们不久便能理解。因为类型为“圆”(Circle)的一个对象也属于类型为“形状”(Shape)的一个对象,因此一个圆彻底能接收发送给"形状”的消息。这意味着可以让程序代码统一指挥“形状”,令其自动控制全部符合“形状”描述的对象,其中天然包括“圆”。这一特性称为对象的“可替换性”,是OOP最重要的概念之一数组

对象更简洁的描述:一个对象具备本身的状态,行为和标识。这意味着对象有本身的内部数据(提供状态)、方法 (产生行为),并彼此区分(每一个对象在内存中都有惟一的地址)。安全

2. 接口数据结构

每一个对象仅能接受特定的请求。咱们向对象发出的请求是经过它的“接口”(Interface)定义的,对象的“类型”或“类”则规定了它的接口形式。“类型”与“接口”的对应关系是面向对象程序设计的基础。编程语言

3. 服务提供工具

软件设计的基本原则是高内聚:每一个组件的内部做用明确,功能紧密相关。

每一个对象都提供了一组紧密的服务。在良好的面向对象设计中,每一个对象功能单一且高效。这样的程序设计能够提升咱们代码的复用性,同时也方便别人阅读和理解咱们的代码。只有让人知道你提供什么服务,别人才能更好地将其应用到其余模块或程序中。

4. 封装

咱们能够把编程的侧重领域划分为研发和应用。应用程序员调用研发程序员构建的基础工具类来作快速开发。研发程序员开发一个工具类,该工具类仅向应用程序员公开必要的内容,并隐藏内部实现的细节。这样能够有效地避免该工具类被错误的使用和更改,从而减小程序出错的可能。彼此职责划分清晰,相互协做。当应用程序员调用研发程序员开发的工具类时,双方创建了关系。应用程序员经过使用现成的工具类组装应用程序或者构建更大的工具库。若是工具类的建立者将类的内部全部信息都公开给调用者,那么有些使用规则就不容易被遵照。由于前者没法保证后者是否会按照正确的规则来使用,甚至是改变该工具类。只有设定访问控制,才能从根本上阻止这种状况的发生。

所以,使用访问控制的缘由有如下2点:

  (1)让应用程序员不要触摸他们不该该触摸的部分。(请注意,这也是一个哲学决策。部分编程语言认为若是程序员有须要,则应该让他们访问细节部分。);

  (2)使类库的建立者(研发程序员)在不影响后者使用的状况下完善更新工具库。例如,咱们开发了一个功能简单的工具类,后来发现能够经过优化代码来提升执行速度。假如工具类的接口和实现部分明确分开并受到保护,那咱们就能够轻松地完成改造。

Java 有三个显式关键字来设置类中的访问权限:public(公开),private(私有),protected(受保护)。这些访问修饰符决定了谁能使用它们修饰的方法、变量或类。

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

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

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

  4. default (默认) 若是你不使用前面的三者,默认就是 default 访问权限。default 被称为包访问,由于该权限下的资源能够被同一包(库组件)中其余类的成员访问

5.  复用

一个类经建立和测试后,理应是可复用的。

代码和设计方案的复用性是面向对象程序设计的优势之一。咱们能够经过重复使用某个类的对象来达到这种复用性。

同时,咱们也能够将一个类的对象做为另外一个类的成员变量使用。新的类能够是由任意数量和任意类型的其余对象构成。

这里涉及到“组合”和“聚合”的概念:

  • 组合(Composition)常常用来表示“拥有”关系(has-a relationship)。例如,“汽车拥有引擎”。
  • 聚合(Aggregation) 动态的 组合。

上图中实心菱形指向“ Car ”表示 组合 的关系;若是是 聚合 关系,可使用空心菱形。

(注:组合和聚合都属于关联关系的一种,只是额外具备总体-部分的意义。至因而聚合仍是组合,须要根据实际的业务需求来判断。可能相同超类和子类,在不一样的业务场景,关联关系会发生变化。只看代码是没法区分聚合和组合的,具体是哪种关系,只能从语义级别来区分。聚合关系中,整件不会拥有部件的生命周期,因此整件删除时,部件不会被删除。再者,多个整件能够共享同一个部件。组合关系中,整件拥有部件的生命周期,因此整件删除时,部件必定会跟着删除。并且,多个整件不能够同时共享同一个部件。这个区别能够用来区分某个关联关系究竟是组合仍是聚合。两个类生命周期不一样步,则是聚合关系,生命周期同步就是组合关系。)

在面向对象编程中常常重点强调“继承”。在新手程序员的印象里,或许先入为主地认为“继承应当随处可见”。沿着这种思路产生的程序设计一般拙劣又复杂。相反,在建立新类时首先要考虑“组合”,由于它更简单灵活,并且设计更加清晰。等咱们有一些编程经验后,一旦须要用到继承,就会明显意识到这一点。

6. 继承

经过使用 class 关键字,这些概念造成了编程语言中的基本单元。遗憾的是,这么作仍是有不少麻烦:在建立了一个类以后,即便另外一个新类与其具备类似的功能,你仍是得从新建立一个新类。但咱们若能利用现成的数据类型,对其进行“克隆”,再根据状况进行添加和修改,状况就显得理想多了。“继承”正是针对这个目标而设计的。但继承并不彻底等价于克隆。在继承过程当中,若原始类(正式名称叫做基础类、超类或父类)发生了变化,修改过的“克隆”类(正式名称叫做继承类或者子类)也会反映出这种变化。

继承经过基本类型和派生类型的概念来表达这种类似性。基本类型包含派生自它的类型之间共享的全部特征和行为。建立基本类型以表示思想的核心。从基类型中派生出其余类型来表示实现该核心的不一样方式。

尽管继承有时意味着你要在接口中添加新方法(尤为是在以extends关键字表示继承的Java中),但并不是总需如此。第二种也是更重要地区分派生类和基类的方法是改变现有基类方法的行为,这被称为覆盖(overriding)。要想覆盖一个方法,只须要在派生类中从新定义这个方法便可。

7. 多态

若是咱们把派生的对象类型统一当作是它自己的基础类型(“圆”看成“形状”,“自行车”看成“车”,“鸬鹚”看成“鸟”等等),编译器(compiler)在编译时期就没法准确地知道什么“形状”被擦除,哪种“车”在行驶,或者是哪一种“鸟”在飞行。

这就是关键所在:当程序接收这种消息时,程序员并不想知道哪段代码会被执行。“绘图”的方法能够平等地应用到每种可能的“形状”上,形状会依据自身的具体类型执行恰当的代码。

若是不须要知道执行了哪部分代码,那咱们就能添加一个新的不一样执行方式的子类而不须要更改调用它的方法。那么编译器在不肯定该执行哪部分代码时是怎么作的呢?

举个例子,下图的 BirdController 对象和通用 Bird 对象中,BirdController 不知道 Bird 的确切类型却还能一块儿工做。从 BirdController 的角度来看,这是很方便的,由于它不须要编写特别的代码来肯定 Bird 对象的确切类型或行为。那么,在调用 move() 方法时是如何保证发生正确的行为(鹅走路、飞或游泳、企鹅走路或游泳)的呢?

经过继承,程序直到运行时才能肯定代码的地址,所以发送消息给对象时,还须要其余一些方案。为了解决这个问题,面向对象语言使用后期绑定的概念。

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

为了执行后期绑定,Java 使用一个特殊的代码位来代替绝对调用。这段代码使用对象中存储的信息来计算方法主体的地址。

 所以,每一个对象的行为根据特定代码位的内容而不一样。当你向对象发送消息时,对象知道该如何处理这条消息。

在某些语言中,必须显式地授予方法后期绑定属性的灵活性。例如,C++ 使用virtual关键字。在这些语言中,默认状况下方法不是动态绑定的。

在 Java 中,动态绑定是默认行为,不须要额外的关键字来实现多态性。

 为了演示多态性,咱们编写了一段代码,它忽略了类型的具体细节,只与基类对话。该代码与具体类型信息分离,所以更易于编写和理解。并且,若是经过继承添加了一个新类型(例如,一个六边形),那么代码对于新类型的 Shape 就像对现有类型同样有效。所以,该程序是可扩展的。

代码示例:

1 void doSomething(Shape shape) {
2     shape.erase();
3     // ...
4     shape.draw();
5 }

此方法与任何 Shape 对话,所以它与所绘制和擦除的对象的具体类型无关。若是程序的其余部分使用 doSomething() 方法:

1 Circle circle = new Circle();
2     Triangle triangle = new Triangle();
3     Line line = new Line();
4     doSomething(circle);
5     doSomething(triangle);
6     doSomething(line);

能够看到不管传入的“形状”是什么,程序都正确的执行了。

 

这是一个很是使人惊奇的编程技巧。分析下面这行代码:

1 doSomething(circle);

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

 这种把子类当成其基类来处理的过程叫作“向上转型”(upcasting)。在面向对象的编程里,常常利用这种方法来给程序解耦。再看下面的 doSomething() 代码示例

1   shape.erase();
2     // ...
3     shape.draw();

咱们能够看到程序并未这样表达:“若是你是一个 Circle ,就这样作;若是你是一个 Square,就那样作…...”。若那样编写代码,就需检查 Shape 全部可能的类型,如圆、矩形等等。这显然是很是麻烦的,并且每次添加了一种新的 Shape 类型后,都要相应地进行修改

。在这里,咱们只需说:“你是一种几何形状,我知道你能删掉 erase() 和绘制 draw() 你本身,去作吧,注意细节。”

尽管咱们确实能够保证最终会为 Shape 调用 erase()、 draw(),但并不能肯定特定的 Circle,Square 或者 Line 调用什么。最后,程序执行的操做却依然是正确的,这是怎么作到的呢?

发送消息给对象时,若是程序不知道接收的具体类型是什么,但最终执行是正确的,这就是对象的“多态性”(Polymorphism)。面向对象的程序设计语言是经过“动态绑定”的方式来实现对象的多态性的

编译器和运行时系统会负责对全部细节的控制;咱们只需知道要作什么,以及如何利用多态性来更好地设计程序。

8. 单继承结构

全部的类都应该默认从一个基类继承:在 Java 中,这个最终的基类的名字就是 Object。

Java 的单继承结构有不少好处。因为全部对象都具备一个公共接口,所以它们最终都属于同一个基本类型。

单继承的结构使得垃圾收集器的实现更为容易。

因为运行期的类型信息会存在于全部对象中,因此咱们永远不会遇到判断不了对象类型的状况。这对于系统级操做尤为重要,例如异常处理。同时,这也让咱们的编程具备更大的灵活性。

 9. 集合

在一些库中,一两个集合泛型集合就能知足咱们全部的需求了,而在其余(Java)中,不一样类型的集合对应不一样的需求:常见的有 List,经常使用于保存序列;Map,也称为关联数组,经常使用于将对象与其余对象关联);Set,只能保存非重复的值;

其余还包括如队列(Queue)、树(Tree)、堆(Stack)等等。

从设计的角度来看,咱们真正想要的是一个可以解决某个问题的集合。若是一种集合就知足全部需求,那么咱们就不须要剩下的了。之因此选择集合有如下两个缘由:

(1)集合能够提供不一样类型的接口和外部行为。堆栈、队列的应用场景和集合、列表不一样,它们中的一种提供的解决方案可能比其余灵活得多。

(2)不一样的集合对某些操做有不一样的效率。例如,List 的两种基本类型:ArrayList 和 LinkedList。虽然二者具备相同接口和外部行为,可是在某些操做中它们的效率差异很大。在 ArrayList 中随机查找元素是很高效的,而 LinkedList 随机查找效率低下。反之,在 LinkedList 中插入元素的效率要比在 ArrayList 中高。因为底层数据结构的不一样,每种集合类型在执行相同的操做时会表现出效率上的差别。

咱们能够一开始使用 LinkedList 构建程序,在优化系统性能时改用 ArrayList。经过对 List 接口的抽象,咱们能够很容易地将 LinkedList 改成 ArrayList。

在 Java 5 泛型出来以前,集合中保存的是通用类型 Object。Java 单继承的结构意味着全部元素都基于 Object 类,因此在集合中能够保存任何类型的数据,易于重用。要使用这样的集合,咱们先要往集合添加元素。

因为 Java 5 版本前的集合只保存 Object,当咱们往集合中添加元素时,元素便向上转型成了 Object,从而丢失本身原有的类型特性。这时咱们再从集合中取出该元素时,元素的类型变成了 Object。那么咱们该怎么将其转回原先具体的类型呢?

这里,咱们使用了强制类型转换将其转为更具体的类型,这个过程称为对象的“向下转型”

经过“向上转型”,咱们知道“圆形”也是一种“形状”,这个过程是安全的。但是咱们不能从“Object”看出其就是“圆形”或“形状”,因此除非咱们能肯定元素的具体类型信息,不然“向下转型”就是不安全的。

也不能说这样的错误就是彻底危险的,由于一旦咱们转化了错误的类型,程序就会运行出错,抛出“运行时异常”(RuntimeException)。(后面的章节会提到)

不管如何,咱们要寻找一种在取出集合元素时肯定其具体类型的方法。另外,每次取出元素都要作额外的“向下转型”对程序和程序员都是一种开销。

以某种方式建立集合,以确认保存元素的具体类型,减小集合元素“向下转型”的开销和可能出现的错误难道很差吗?这种解决方案就是:参数化类型机制(Parameterized Type Mechanism)

 参数化类型机制可使得编译器可以自动识别某个 class 的具体类型并正确地执行。

举个例子,对集合的参数化类型机制可让集合仅接受“形状”这种类型的元素,并以“形状”类型取出元素。Java 5 版本支持了参数化类型机制,称之为“泛型”(Generic)。泛型是 Java 5 的主要特性之一。你能够按如下方式向 ArrayList 中添加 Shape(形状):

1 ArrayList<Shape> shapes = new ArrayList<>();

 10. 对象建立与生命周期

每一个对象的生存都须要资源,尤为是内存。为了资源的重复利用,当对象再也不被使用时,咱们应该及时释放资源,清理内存。

Java 使用动态内存分配。每次建立对象时,使用 new 关键字构建该对象的动态实例。这又带来另外一个问题:对象的生命周期。较之堆内存,在栈内存中建立对象,编译器可以肯定该对象的生命周期并自动销毁它;

然而若是你在堆内存建立对象的话,编译器是不知道它的生命周期的。在 C++ 中你必须以编程方式肯定什么时候销毁对象,不然可能致使内存泄漏。Java 的内存管理是创建在垃圾收集器上的,它能自动发现对象再也不被使用并释放内存。

垃圾收集器的存在带来了极大的便利,它减小了咱们以前必需要跟踪的问题和编写相关代码的数量。

Java 的垃圾收集器被设计用来解决内存释放的问题(虽然这不包括对象清理的其余方面)。垃圾收集器知道对象何时再也不被使用而且自动释放内存。

11. 异常处理

异常处理机制将程序错误直接交给编程语言甚至是操做系统。

“异常”(Exception)是一个从出错点“抛出”(thrown)后能被特定类型的异常处理程序捕获(catch)的一个对象。它不会干扰程序的正常运行,仅当程序出错的时候才被执行。

最后,“异常机制”提供了一种可靠地从错误情况中恢复的方法,使得咱们能够编写出更健壮的程序。有时你只要处理好抛出的异常状况并恢复程序的运行便可,无需退出。

完!

转载:https://github.com/LingCoder/OnJava8/blob/master/docs/book/01-What-is-an-Object.md

相关文章
相关标签/搜索