对象的概念
面向对象编程(Object-Oriented Programming OOP)是一种编程思维方式和编码架构。
1. 抽象
- Smalltalk 作为第一个成功的面向对象并影响了 Java 的程序设计语言。
- 五大基本特征:(Alan Kay )
- 万物皆对象
- 程序是一组对象,通过消息传递来告知彼此该做什么
- 每个对象都有自己的存储空间,可容纳其他对象
- 每个对象都有一种类型
- 同一类所有对象都能接收相同的消息
- 一个对象具有自己的状态,行为和标识。这意味着对象有自己的内部数据(提供状态)、方法 (产生行为),并彼此区分(每个对象在内存中都有唯一的地址)。
2. 接口
- 每个对象仅能接受特定的请求。我们向对象发出的请求是通过它的“接口”(Interface)定义的,对象的“类型”或“类”则规定了它的接口形式。“类型”与“接口”的对应关系是面向对象程序设计的基础。
- UML(Unified Modeling Language,统一建模语言)
3. 服务提供
- 在开发或理解程序设计时,我们可以将对象看成是“服务提供者”。
- 我们的最终目标是开发或调用工具库中已有的一些对象,提供理想的服务来解决问题。
- 在良好的面向对象设计中,每个对象功能单一且高效。这样的程序设计可以提高我们代码的复用性,同时也方便别人阅读和理解我们的代码。只有让人知道你提供什么服务,别人才能更好地将其应用到其他模块或程序中。
4. 封装
- 对象仅向应用程序员公开必要的内容,并隐藏内部实现的细节。这样可以有效地避免该工具类被错误的使用和更改,从而减少程序出错的可能。彼此职责划分清晰,相互协作。
- 使用访问控制的原因有以下两点:
- 让应用程序员不要触摸他们不应该触摸的部分。
- 使类库的创建者(研发程序员)在不影响后者使用的情况下完善更新工具库
- Java 访问控制权限关键字:
public
(公开)表示任何人都可以访问和使用该元素。private
(私有)除了类本身和类内部的方法,外界无法直接访问该元素。private
是类和调用者之间的屏障。任何试图访问私有成员的行为都会报编译时错误。protected
(受保护)类似于private
,区别是子类(下一节就会引入继承的概念)可以访问protected
的成员,但不能访问private
成员。default
(默认)如果你不使用前面的三者,默认就是default
访问权限。default
被称为包访问,因为该权限下的资源可以被同一包(库组件)中其他类的成员访问。
5. 复用
- 代码和设计方案的复用性是面向对象程序设计的优点之一。
- 我们可以通过重复使用某个类的对象来达到这种复用性。同时,我们也可以将一个类的对象作为另一个类的成员变量使用。新的类可以是由任意数量和任意类型的其他对象构成。
- 组合(Composition)经常用来表示“拥有”关系(has-a relationship)。例如,“汽车拥有引擎”。
- 聚合(Aggregation)动态的组合。
6. 继承
-
为什么有继承?
在创建了一个类之后,即使另一个新类与其具有相似的功能,你还是得重新创建一个新类。但我们若能利用现成的数据类型,对其进行“克隆”,再根据情况进行添加和修改,情况就显得理想多了。“继承”正是针对这个目标而设计的。
-
在继承过程中,若原始类(正式名称叫作基类、超类或父类)发生了变化,修改过的“克隆”类(正式名称叫作继承类或者子类)也会反映出这种变化。
-
区分新的派生类与原始的基类:
- 在派生类中添加新方法。这些新方法不是基类接口的一部分。这意味着基类不能满足你的所有需求,所以你添加了更多的方法。继承的这种简单而原始的用途有时是解决问题的完美解决方案。
- 更重要地区分派生类和基类的方法是改变现有基类方法的行为,这被称为覆盖 (overriding)。要想覆盖一个方法,只需要在派生类中重新定义这个方法即可。
-
我们经常把这种基类和派生类的关系称为是一个(is-a)关系,有时你在派生类添加了新的接口元素,从而扩展接口。虽然新类型仍然可以替代基类,但是这种替代不完美,原因在于基类无法访问新添加的方法。这种关系称为像是一个(**is-like-a)**关系。
7. 多态
- 早期绑定 / 静态绑定:编译器生成对特定函数名的调用,该调用会被解析为将执行的代码的绝对地址。
- 后期绑定 / 动态绑定:当向对象发送信息时,被调用的代码直到运行时才确定。编译器确保方法存在,并对参数和返回值执行类型检查,但是它不知道要执行的确切代码。
- 为了执行后期绑定,Java 使用一个特殊的代码位来代替绝对调用。这段代码使用对象中存储的信息来计算方法主体的地址。因此,每个对象的行为根据特定代码位的内容而不同。当你向对象发送消息时,对象知道该如何处理这条消息。在某些语言中,必须显式地授予方法后期绑定属性的灵活性。例如,C++ 使用 virtual 关键字。在这些语言中,默认情况下方法不是动态绑定的。在 Java 中,动态绑定是默认行为,不需要额外的关键字来实现多态性。
8. 单继承结构
- Java 中所有类的最终基类为 Object。
- Java 的单继承结构有很多好处:由于所有对象都具有一个公共接口,因此它们最终都属于同一个基类。另外,单继承的结构使得垃圾收集器的实现更为容易。由于运行期的类型信息会存在于所有对象中,所以我们永远不会遇到判断不了对象类型的情况。
9. 对象创建与生命周期
-
对象的创建主要分为在栈中静态创建以及在堆内存(Heap)中动态地创建对象。
- 在堆内存上开辟空间所需的时间可能比在栈内存上要长(但也不一定)。
- 在栈内存开辟和释放空间通常是一条将栈指针向下移动和一条将栈指针向上移动的汇编指令。
- 开辟堆内存空间的时间取决于内存机制的设计。
-
Java 使用动态内存分配。每次创建对象时,使用
new
关键字构建该对象的动态实例。 -
动态方法有这样一个合理假设:对象通常是复杂的,相比于对象创建的整体开销,寻找和释放内存空间的开销微不足道。
-
较之堆内存,在栈内存中创建对象,编译器能够确定该对象的生命周期并自动销毁它;
-
在 C++ 中你必须以编程方式确定何时销毁对象,否则可能导致内存泄漏。Java 的内存管理是建立在垃圾收集器上的,它能自动发现对象不再被使用并释放内存。垃圾收集器的存在带来了极大的便利,它减少了我们之前必须要跟踪的问题和编写相关代码的数量。因此,垃圾收集器提供了更高级别的保险,以防止潜在的内存泄漏问题。