Java面向对象编程详细介绍 | Java OOP Detailed Introduction
Made by Mike_Zhang
1. Intro
1. 引入
思考:
我想把大象放进冰箱,该怎么做?
很简单。
- 第一步让大象走到冰箱前面;
- 第二步让冰箱门打开;
- 第三部让大象走进去并站在里面;
- 最后一步让冰箱门关上。
我们试着用Python来模拟一下:
1 |
|
以上是常见的经典的 面向过程编程 (Procedural-Oriented Programming, POP) 。以过程为导向,把任务拆解成一个小的步骤。
正如书 Algorithms plus data structures equals programs (Niklaus Wirth, 1976)中所描述的一样,POP首先设计算法,需要如何去操作数据,然后再去设计对应的数据结构。
再思考:
我还想把大象放进电饭煲,该怎么做?
还是很简单。
- 第一步让大象走到电饭煲前面;
- 第二步让电饭煲打开;
- 第三部让大象跳进去站在里面;
- 最后一步让电饭煲关上。
但是我们发现,用POP设计的算法,一旦我们的需求有一点点变化,就需要修改整个算法,因为大象进入电饭煲的步骤是不一样的。
而我们接下来讨论的面向对象编程恰恰相反,需要的数据结构事先被设计好,我们再需要设计的是算法,也就是如何操作这些数据。
面向对象编程,顾名思义,首先你需要对象,以我们的大象放进冰箱为例子:
- 确定这个问题有两个对象,大象和冰箱。
- 接下来,我们操作大象这个对象,把它放到冰箱里,完成了。
1 |
|
至于大象是怎么运动的,冰箱是怎么开门的,作为大象和冰箱用户的我们并不需要关心,我们只需要知道怎么使用它们就可以。大象和冰箱实现运动的细节就交给大象和冰箱背后的程序员来负责。
这样的好处就是,一旦任务需求有点变化,比如要把大象放进电饭煲,只要改变一点就可以:
- 确定这个问题有两个对象,大象和电饭煲。
- 接下来,我们操作大象这个对象,把它放到电饭煲里,完成了。
1 |
|
1.1 Object-Oriented Programming
1.1 面向对象编程
- 面向对象编程 (Object-Oriented Programming, OOP) 是一种计算机编程架构,主要处理目标是 对象 。而 类 是对一群对象的 抽象 ,类描述了其下对象的 变量数据 和 可执行的操作 。每一个对象只会对其用户 开放 一些 特定的操作 ,同时 隐藏 了其 实现的细节 。
主要价值:
- 可靠性 (Reliability) :
- 面向对象编程基于不同的对象,每个对象只提供不同的功能给用户使用,而内部的原理和细节会被隐藏,使其收到保护,提高可靠性;
- 可重用性 (Reusability) :
- 能基于现有的对象,继承出新的类似的对象,并且能够直接拥有被继承对象的特点和方法,使其可以重复使用已有功能,提高可重用性;
- 适应性 (Adaptability) :
- 能基于现有的对象,拓展出新的类似的对象,并拥有额外的功能,使其可以适应新的需求,提高适应性;
- 低耦合 (Low Coupling) :
- 需求的实现过程被抽象成一个个对象,每个对象都是相对独立的,某一对象出现问题,不会影响其他对象,提高低耦合性;
2. Class and Object
2. 类和对象
现实生活中,任意一个具体的事物都是一个 对象 (Object) 。
- 而一群有着类似特征和行为的对象可以被抽象成一个 类 (Class) ,类就像是一个模版或模型,是对事物的一种抽象。在Java中,Class是一种数据类型。
- 而从这一类中创造的具体的事物就会是一个对象,也被称为这此类的一个 实例 (Instance) 。一个类能够创建多个实例,它们类型相同,但是内部细节各不相同。
举例来说:
- 人 是一种抽象的类,这一类规定了具体人的特征和行为,比如年龄和动作。
而 王某某,李某某,张某某 等具体的某一个人就是人这一类的对象或实例,
并且 张某某 这一具体的对象有具体的年龄特征和具体的动作行为,比如21岁和写博客。
2.1 Class Definition
2.1 类的定义
一般来说,一个类被定义成两个部分,静态部分和动态部分。
- 静态部分:类的 属性 (field) 。用来描述此类实例的特征或状态,如,人的姓名、年龄、性别、身高等。这个部分会存储在此类实例的内存中。
- 动态部分:类的 方法 (method) 。定义了此类实例的行为,能够对此类实例进行的操作,如,人的动作、说话、走路等。这个部分不会存储在此类实例的内存中。
1 |
|
2.2 Object Creation
2.2 对象创建
上一节完成了类的定义,但是类只是一个抽象,只有真正此类的实例或对象才有意义。在Java中,我们可以通过new
关键字来创建一个某类的实例或对象。
如:
1 |
|
此处,
Person mike
:是对Mike
变量的 声明 (Declaration) ,声明了此变量的名字,类型,可选择性地规定初始值;new Person()
:是对Person
类的 实例化 (Instantiation) 或 创建(Creation) ,创建了一个Person
类的对象;Mike = new Person()
:是对Mike
变量的 初始化 (initialization) 或 定义(Definition) ,将Mike
变量指向此对象(把此对象赋值给Mike
变量)。
当然,我们可以创建多个实例,比如:
1 |
|
2.2.1 Object Field Access
2.2.1 对象属性访问
实例(或对象)的属性可以通过实例名.属性名
的方式来访问和赋值,比如:
1 |
|
虽然这三个对象都是Person
类的实例,但是他们同一个属性是相互独立,不会影响的。比如修改Mike.name
,不会影响Alice.name
和Bob.name
。
因为每一个实例的属性都是存储在它自己独有的空间内的。
2.2.2 Object Method Invocation
2.2.2 对象方法调用
实例(或对象)的行为(方法)可以通过实例名.方法名(参数)
(参数可选) 的形式来调用,比如:
1 |
|
一个类的的每个实例共享了此类的所有的方法,因此实例并不会存储其方法在内存空间中。但是每个实例会根据用户所调用的方法,来选择其对应的行为。
2.2.3 Object Identity
2.2.3 对象的标识
上节说到每一个实例(或对象)只会储存其静态部分(属性)在其内存空间中,
那这是否意味着两个拥有相同属性和属性值的实例是相等的?
并不是。属性并不能完全描述一个实例,每个实例是存储在不同的内存空间中的,都是独一无二的。
如,两个双胞胎,尽管他们的性别,年龄,姓名(假设相同)都相同,但是他们还是两个不同的个体。但他们的唯一标识(比如身份证)可以区分开来。
2.3 Class Relationships
2.3 类间关系
Dependence (“uses–a”) 关系:
- 比如,人类用到了天气类,因为一个人的行为会被天气影响到,有依托的关系;
Aggregation (“has–a”) 关系:
- 一个A类包括了另一个B类。A类有B类的一个属性,或A类中创建了B类的一个实例,或A类的方法中有B类的一个参数。
- 比如,人类中有一个工作类,因为一个人有工作这一属性。人类也可以有一个是人类,因为可以用来表示亲人、朋友这些属性。
Inheritance (“is–a”) 关系:
- 能够表示相对一般的类和相对具体的类之间的关系,被称为 继承 (Inheritance) 的关系。继承类能够继承被继承类的属性和方法,同时也可以有自己特有的部分。
- 比如,学生 类是人类中的一种相对具体的类,因此学生类可以继承人类。常识告诉我们,学生同时拥有人的属性和方法。与此同时,学生还能够拥有一些特有的属性和方法,比如,年纪、学校等属性,进入学校、更新学校等行为。
以上我们初步了解了Java中面向对象编程的基本概念,以及类和对象的关系和应用。
接下来我们介绍面向对象编程的三个经典特性:
封装、继承、多态。
3. Encapsulation
3. 封装
- 封装 (Encapsulation) 是指把对象的内部属性 和 行为的实现细节 隐藏起来,使得对象的内部属性和行为只能通过公共接口来访问。如图:
比如:
- 大象装进冰箱只需要
elephant.putIn(fridge);
即可,不需要访问过程的实现细节; - 若用户直接能够操作冰箱对象内部的属性或者开门的实现细节,那我可以让冰箱永远打不开门或者让其断电(:P)。
目的:
- 某一类的程序员可以自由设计类的内部属性和行为的实现方法,无需关心使用此类的其他类程序员会受到其内部实现细节的影响;
- 此类的程序员只需关心此类的行为对外的开放接口,如此类行为的名称、参数类型、返回值类型、用法等,能够让其他程序员使用这些接口;
- 能够提高可靠性和适应性,因为内部行为实现细节的改变不会影响到其他部分以及其用户。程序员只改变内部实现细节,就能方便的维护和增加新的功能。
在阅读完本文章后,你可以参考本博客中的 Java面向对象编程之 封装性 | Java OOP Encapsulation 这一文章,了解更多关于继承的内容。
4. Inheritance
4. 继承
- 继承 (Inheritance) 是一种用于多层级的类之间的机制。在这些类中,一些通用的方法可以被组合在一个最常规的类中,叫做 父类(super class) 。而有着一些额外功能或方法的特殊类,可以看作常规类的延伸,叫做 子类(sub class) 。我们称子类 继承 (extends) 父类,子类会同时继承父类的大部分的属性和方法。
比如:
- 上文提到的人类,可以作为一个父类,包括类所有人的通用的属性和方法。而学生类可以作为其的一个子类,继承了人类。那么学生类拥有人类通用的属性和行为,同时还拥有学校和上学这些特有的属性和行为。这也是我们上文提到的Inheritance (“is–a”) 关系。
目的:
- 能够提高代码的可重用性,因为子类能够直接继承父类中的属性和方法,这些相同的代码就不需要在子类中重新写一遍。子类中只需要加入其不同于父类的属性和方法即可;
- 当然,子类也可以重写从父类继承过来的方法,成为自己的新的方法。
在阅读完本文章后,你可以参考本博客中的 Java面向对象编程(OOP)的继承性(Inheritance) 这一文章,了解更多关于继承的内容。
5. Polymorphism
5. 多态
- 多态 (Polymorphism) 是一种在继承中作用于方法的机制。一个多态的方法,在被不同子类的对象调用时,能灵活地做出不同的对应行为。能够使一个方法有多种状态。
比如:
1.我们创建两个人类的实例,学生甲
和老师乙
,分别为学生类和老师类,代码如下:
1 |
|
注:Java允许一个子类的实例赋值给一个其父类类型的变量,称为向上转型。
2.并且我们在人类中定义一个方法,来让人类实例介绍自己,代码如下:
1 |
|
3.并且分别在人类的子类学生类和老师类中定义同名的类似的方法,代码如下:
1 |
|
1 |
|
4.现在我们定义一个舞台,让人类可以介绍自己,代码如下:
1 |
|
5.现在,邀请学生甲
和老师乙
在舞台上介绍自己:
1 |
|
可以看到,学生甲
和老师乙
都能具体的介绍自己,不会只说“我是一个人类”,而是说“我是一个学生”或者“我是一个老师”。
而这就是方法introduce()
多态的表现,能够根据不同类型的对象,做出对应的不同的表现。
目的:
- 增加了代码的拓展性或适应性,使方法的调用更加灵活。
- 比如:
- 在舞台的
表演
方法中,只需要请人类上台,并让他们介绍
就可以,不用判断是老师还是学生并设计不同的表演方式。 - 如果新的人类子类出现,比如
医生
,我们只需要定义好医生类应该如何介绍自己,而不用修改舞台类,降低了拓展代码的成本。
- 在舞台的
在阅读完本文章后,你可以参考本博客中的 Java面向对象编程(OOP)的多态性(Polymorphism) 这一文章,了解更多关于多态的内容。
6. Takeaway
6. 总结
面向对象编程是一种非常强大的编程方法,把现实生活中的对象引入计算机中,并进行一系列的抽象,并让不同模型和对象互相引用,使得程序模型更加灵活,强大,更好维护与拓展。
但是,在带来一系列好处的同时,面向对象一定程度上增加了代码的复杂度。你应该不想为了只输出一句
Hello, World!
而写一个类,来实现这个功能吧?
因此,如果你的代码需求很明确,只是为了实现一个固定的功能,并不会有后期的拓展或者修改,不会有很复杂的结构,我推荐直接用POP来写代码。比如输出一句Hello, World!
。
希望本文章能够对你理解Java中的面向对象编程有一些帮助。
如有任何疑问,或者发现本文任何错误,请在文章下方留言!
若想了解更多有关Java及其面向对象编程,可参考:
Java主题:
References
参考
C. S. Horstmann, Core Java. Boston: Pearson, 2019.
M. Goodrich, R. Tamassia, and A. O’reilly, Data Structures and Algorithms in Java, 6th Edition. John Wiley & Sons, 2014.
Niklaus Wirth. (1976). Algorithms plus data structures equals programs. Englewood Cliffs, N.J. Prentice-Hall.
面向对象编程 - 廖雪峰的官方网站
怎么从本质上理解面向对象的编程思想?
Outro
尾巴
Java中OOP相关的知识是十分重要的, 会继续更新.
最后,希望大家一起交流,分享,指出问题,谢谢!
原创文章,转载请标明出处
Made by Mike_Zhang