文章目录

  • 第一章 对象的概念
  • 什么是“纯粹”的面向对象程序设计方法
  • 接口、
  • 封装
  • 什么是封装?
  • 为什么使用访问控制权限?
  • 访问控制权限修饰符
  • 复用
  • 继承
  • 多态
  • 多态的优点
  • 要注意的地方
  • 解决的方法
  • 具体原理
  • 单继承结构
  • 单继承的好处
  • 集合
  • 集合诞生的原因
  • 集合的优点
  • java类库中的集合类型
  • 为什么选择集合?
  • 异常处理


最后更新时间:2020.05.09

本笔记大部分基于《on java 8》整理, 另外初学者一枚,大家多多关照,有错误可以在下面说出来 谢谢大家

第一章 对象的概念

什么是“纯粹”的面向对象程序设计方法

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

Grady Booch 提供了对对象更简洁的描述

一个对象具有自己的状态,行为和标识。这意味着对象有自己的内部数据(提供状态)、方法 (产生行为),并彼此区分(每个对象在内存中都有唯一的地址)。

接口、

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

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

软件设计的基本原则

高内聚:每个组件的内部作用明确,功能紧密相关。

在良好的面向对象设计中,每个对象功能单一且高效。这样的程序设计可以提高我们代码的复用性,同时也方便别人阅读和理解我们的代码。

封装

什么是封装?

仅向应用程序员公开必要的内容,并隐藏内部实现的细节。这样可以有效地避免该工具类被错误的使用和更改,从而减少程序出错的可能。
只有设定访问控制,才能从根本上阻止这种情况的发生。

为什么使用访问控制权限?

  1. 让应用程序员不要触摸他们不应该触摸的部分。(请注意,这也是一个哲学决策。部分编程语言认为如果程序员有需要,则应该让他们访问细节部分。);
  2. 使类库的创建者(研发程序员)在不影响后者使用的情况下完善更新工具库。例如,我们开发了一个功能简单的工具类,后来发现可以通过优化代码来提高执行速度。假如工具类的接口和实现部分明确分开并受到保护,那我们就可以轻松地完成改造

访问控制权限修饰符

  1. public(公开)表示任何人都可以访问和使用该元素;
  2. private(私有)除了类本身和类内部的方法,外界无法直接访问该元素。
    private 是类和调用者之间的屏障。任何试图访问私有成员的行为都会报编译时错误;
  3. protected(受保护)类似于 private,区别是子类可以访问 protected 的成员,但不能访问 private 成员;
  4. default(默认)如果你不使用前面的三者,默认就是 default 访问权限。
    default 被称为包访问,因为该权限下的资源可以被同一包(库组件)中其他类的成员访问

复用

代码和设计方案的复用性是面向对象程序设计的优点之一。
我们可以通过重复使用某个类的对象来达到这种复用性。同时,我们也可以将一个类的对象作为另一个类的成员变量使用。新的类可以是由任意数量和任意类型的其他对象构成。这里涉及到“组合”和“聚合”的概念:

  • 组合(Composition)经常用来表示“拥有”关系(has-a relationship)。例如,“汽车拥有引擎”。
  • 聚合(Aggregation)动态的组合
    组合和聚合的相同点
    组合和聚合都属于关联关系的一种,只是额外具有整体-部分的意义
    组合和聚合的不同点
    组合关系中,整件拥有部件的生命周期,所以整件删除时,部件一定会跟着删除。而且,多个整件不可以同时共享同一个部件。这个区别可以用来区分某个关联关系到底是组合还是聚合。两个类生命周期不同步,则是聚合关系,生命周期同步就是组合关系。

继承

“继承”给面向对象编程带来极大的便利。它在概念上允许我们将各式各样的数据和功能封装到一起,这样便可恰当表达“问题空间”的概念,而不用受制于必须使用底层机器语言。

多态

我们在处理类的层次结构时,通常把一个对象看成是它所属的基类,而不是把它当成具体类。通过这种方式,我们可以编写出不局限于特定类型的代码。所有的形状都可以被绘制、擦除和移动,因此“方法”向其中的任何代表“形状”的对象发送消息都不必担心对象如何处理信息。

多态的优点

这样的代码不会受添加的新类型影响,并且添加新类型是扩展面向对象程序以处理新情况的常用方法。 通过派生新的子类来扩展设计的这种能力是封装变化的基本方法之一。
这种能力改善了我们的设计,且减少了软件的维护代价。

要注意的地方

如果我们把派生的对象类型统一看成是它本身的基类(“圆”当作“形状”,“自行车”当作“车”,“鸬鹚”当作“鸟”等等),编译器(compiler)在编译时期就无法准确地知道什么“形状”被擦除,哪一种“车”在行驶,或者是哪种“鸟”在飞行。这就是关键所在:当程序接收这种消息时,程序员并不想知道哪段代码会被执行。“绘图”的方法可以平等地应用到每种可能的“形状”上,形状会依据自身的具体类型执行恰当的代码。

如果不需要知道执行了哪部分代码,那我们就能添加一个新的不同执行方式的子类而不需要更改调用它的方法。那么编译器在不确定该执行哪部分代码时是怎么做的呢?

解决的方法

这个问题的答案,是面向对象程序设计的妙诀:在传统意义上,编译器不能进行函数调用。由非 OOP 编译器产生的函数调用会引起所谓的早期绑定,这个术语你可能从未听说过,不会想过其他的函数调用方式。这意味着编译器生成对特定函数名的调用,该调用会被解析为将执行的代码的绝对地址。

通过继承,程序直到运行时才能确定代码的地址,因此发送消息给对象时,还需要其他一些方案。为了解决这个问题,面向对象语言使用后期绑定的概念。当向对象发送信息时,被调用的代码直到运行时才确定。编译器确保方法存在,并对参数和返回值执行类型检查,但是它不知道要执行的确切代码。

具体原理

为了执行后期绑定,Java 使用一个特殊的代码位来代替绝对调用。这段代码使用对象中存储的信息来计算方法主体的地址(此过程在多态性章节中有详细介绍)。因此,每个对象的行为根据特定代码位的内容而不同。当你向对象发送消息时,对象知道该如何处理这条消息。在某些语言中,必须显式地授予方法后期绑定属性的灵活性。例如,C++ 使用 virtual 关键字。在这些语言中,默认情况下方法不是动态绑定的。在 Java 中,动态绑定是默认行为,不需要额外的关键字来实现多态性。

示例

它忽略了类型的具体细节,只与基类对话。该代码与具体类型信息分离,因此更易于编写和理解。而且,如果通过继承添加了一个新类型(例如,一个六边形),那么代码对于新类型的 Shape 就像对现有类型一样有效。因此,该程序是可扩展的。

void doSomething(Shape shape) {
    shape.erase();
    // ...
    shape.draw();
}

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

Circle circle = new Circle();
    Triangle triangle = new Triangle();
    Line line = new Line();
    doSomething(circle);
    doSomething(triangle);
    doSomething(line);

doSomething(circle);

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

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

shape.erase();
    // ...
    shape.draw();

发送消息给对象时,如果程序不知道接收的具体类型是什么,但最终执行是正确的,这就是对象的“多态性”(Polymorphism)。面向对象的程序设计语言是通过“动态绑定”的方式来实现对象的多态性的。编译器和运行时系统会负责对所有细节的控制;我们只需知道要做什么,以及如何利用多态性来更好地设计程序。

**总结:**编译时看父类,运行时看子类

单继承结构

java是单继承的,只允许同时继承一个类,不允许像C++里面一样允许多继承。
java有一个最终父类:object

单继承的好处

1.由于所有对象都具有一个公共接口,因此它们最终都属于同一个基类。
2.避免了菱形继承导致的混乱
3.单继承的结构使得垃圾收集器的实现更为容易
4.由于运行期的类型信息会存在于所有对象中,所以我们永远不会遇到判断不了对象类型的情况。这对于系统级操作尤其重要,例如异常处理。同时,这也让我们的编程具有更大的灵活性。

集合

集合诞生的原因

通常,我们并不知道解决某个具体问题需要的对象数量和持续时间,以及对象的存储方式。那么我们如何知悉程序在运行时需要分配的内存空间呢?
于是java集合诞生了

集合的优点

“集合”这种类型的对象可以存储任意类型、数量的其他对象。它能根据需要自动扩容,我们目前不用关心过程是如何实现的。

java类库中的集合类型

常见的有 List,常用于保存序列;Map,也称为关联数组,常用于将对象与其他对象关联);Set,只能保存非重复的值;其他还包括如队列(Queue)、树(Tree)、栈(Stack)、堆(Heap)等等。

为什么选择集合?

  1. 集合可以提供不同类型的接口和外部行为。堆栈、队列的应用场景和集合、列表不同,它们中的一种提供的解决方案可能比其他灵活得多。
  2. 不同的集合对某些操作有不同的效率。例如,List 的两种基本类型:ArrayList 和 LinkedList。虽然两者具有相同接口和外部行为,但是在某些操作中它们的效率差别很大。在 ArrayList 中随机查找元素是很高效的,而 LinkedList 随机查找效率低下。反之,在 LinkedList 中插入元素的效率要比在 ArrayList 中高。由于底层数据结构的不同,每种集合类型在执行相同的操作时会表现出效率上的差异。

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

异常处理

异常处理机制将程序错误直接交给编程语言甚至是操作系统。“异常”(Exception)是一个从出错点“抛出”(thrown)后能被特定类型的异常处理程序捕获(catch)的一个对象。它不会干扰程序的正常运行,仅当程序出错的时候才被执行。

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

注:java是内置异常的

本笔记大部分基于《on java 8》整理, 另外初学者一枚,大家多多关照,有错误可以在下面说出来 谢谢大家