Java面向对象编程详细介绍 | Java OOP Detailed Introduction

Made by Mike_Zhang


1. Intro

1. 引入

思考

我想把大象放进冰箱,该怎么做?

很简单。

  1. 第一步让大象走到冰箱前面;
  2. 第二步让冰箱门打开;
  3. 第三部让大象走进去并站在里面;
  4. 最后一步让冰箱门关上。

我们试着用Python来模拟一下:

1
2
3
4
5
elephant.moveForward(5) # 大象向前走5步到冰箱前面
fridge.open() # 冰箱门打开
elephant.moveForward(2) # 大象向前走2步进冰箱
elephant.stop() # 大象停下
fridge.close() # 冰箱门关上

以上是常见的经典的 面向过程编程 (Procedural-Oriented Programming, POP) 。以过程为导向,把任务拆解成一个小的步骤。

正如书 Algorithms plus data structures equals programs (Niklaus Wirth, 1976)中所描述的一样,POP首先设计算法,需要如何去操作数据,然后再去设计对应的数据结构。

再思考

我还想把大象放进电饭煲,该怎么做?

还是很简单。

  1. 第一步让大象走到电饭煲前面;
  2. 第二步让电饭煲打开;
  3. 第三部让大象跳进去站在里面;
  4. 最后一步让电饭煲关上。

但是我们发现,用POP设计的算法,一旦我们的需求有一点点变化,就需要修改整个算法,因为大象进入电饭煲的步骤是不一样的。


而我们接下来讨论的面向对象编程恰恰相反,需要的数据结构事先被设计好,我们再需要设计的是算法,也就是如何操作这些数据。

面向对象编程,顾名思义,首先你需要对象,以我们的大象放进冰箱为例子:

  1. 确定这个问题有两个对象,大象冰箱
  2. 接下来,我们操作大象这个对象,把它放到冰箱里,完成了。
1
2
3
4
5
6
# 1
elephant = Elephant()
fridge = Fridge()

# 2
elephant.putIn(fridge)

至于大象是怎么运动的,冰箱是怎么开门的,作为大象和冰箱用户的我们并不需要关心,我们只需要知道怎么使用它们就可以。大象和冰箱实现运动的细节就交给大象和冰箱背后的程序员来负责。

这样的好处就是,一旦任务需求有点变化,比如要把大象放进电饭煲,只要改变一点就可以:

  1. 确定这个问题有两个对象,大象电饭煲
  2. 接下来,我们操作大象这个对象,把它放到电饭煲里,完成了。
1
2
3
4
5
6
# 1
elephant = Elephant()
cooker = Cooker()

# 2
elephant.putIn(cooker)

1.1 Object-Oriented Programming

1.1 面向对象编程

  • 面向对象编程 (Object-Oriented Programming, OOP) 是一种计算机编程架构,主要处理目标是 对象 。而 是对一群对象的 抽象 ,类描述了其下对象的 变量数据可执行的操作 。每一个对象只会对其用户 开放 一些 特定的操作 ,同时 隐藏 了其 实现的细节

主要价值:

  • 可靠性 (Reliability)
    • 面向对象编程基于不同的对象,每个对象只提供不同的功能给用户使用,而内部的原理和细节会被隐藏,使其收到保护,提高可靠性;
  • 可重用性 (Reusability)
    • 能基于现有的对象,继承出新的类似的对象,并且能够直接拥有被继承对象的特点和方法,使其可以重复使用已有功能,提高可重用性;
  • 适应性 (Adaptability)
    • 能基于现有的对象,拓展出新的类似的对象,并拥有额外的功能,使其可以适应新的需求,提高适应性;
  • 低耦合 (Low Coupling)
    • 需求的实现过程被抽象成一个个对象,每个对象都是相对独立的,某一对象出现问题,不会影响其他对象,提高低耦合性;

Procedural vs. OO programming (Horstmann, 2019.)


2. Class and Object

2. 类和对象

现实生活中,任意一个具体的事物都是一个 对象 (Object)

  • 一群有着类似特征和行为的对象可以被抽象成一个 类 (Class)就像是一个模版或模型,是对事物的一种抽象。在Java中,Class是一种数据类型。
  • 而从这一中创造的具体的事物就会是一个对象,也被称为这此的一个 实例 (Instance) 。一个能够创建多个实例,它们类型相同,但是内部细节各不相同。

举例来说:

  • 是一种抽象的类,这一类规定了具体人的特征和行为,比如年龄和动作。
    王某某李某某张某某 等具体的某一个人就是这一类的对象实例
    并且 张某某 这一具体的对象有具体的年龄特征具体的动作行为,比如21岁写博客

2.1 Class Definition

2.1 类的定义

一般来说,一个类被定义成两个部分,静态部分动态部分

  • 静态部分:类的 属性 (field) 。用来描述此类实例的特征或状态,如,人的姓名、年龄、性别、身高等。这个部分会存储在此类实例的内存中。
  • 动态部分:类的 方法 (method) 。定义了此类实例的行为,能够对此类实例进行的操作,如,人的动作、说话、走路等。这个部分不会存储在此类实例的内存中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Person { // 人类

// 静态部分 - 属性
String name; // 姓名
int age; // 年龄
boolean gender; // 性别 true-男 false-女

// 动态部分 - 方法
public void walk() { // 走路
System.out.println("I'm walking.");
}

public void speak() { // 说话
System.out.println("I'm speaking.");
}

public int howOld() { // 获取年龄
System.out.println(age); // 获取实例的age属性值
return age;
}

public void updateName(String newName) { // 更新姓名
name = newName; // 更新实例的name属性值
}
}


2.2 Object Creation

2.2 对象创建

上一节完成了类的定义,但是类只是一个抽象,只有真正此类的实例或对象才有意义。在Java中,我们可以通过new关键字来创建一个某类的实例对象

如:

1
Person Mike = new Person(); // 创建一个人类的实例,并由变量mike指向此实例

此处,

  • Person mike:是对Mike变量的 声明 (Declaration) ,声明了此变量的名字,类型,可选择性地规定初始值;
  • new Person():是对Person类的 实例化 (Instantiation)创建(Creation) ,创建了一个Person类的对象;
  • Mike = new Person():是对Mike变量的 初始化 (initialization)定义(Definition) ,将Mike变量指向此对象(把此对象赋值给Mike变量)。

当然,我们可以创建多个实例,比如:

1
2
3
Person Mike = new Person();
Person Alice = new Person();
Person Bob = new Person();

2.2.1 Object Field Access

2.2.1 对象属性访问

实例(或对象)的属性可以通过实例名.属性名的方式来访问和赋值,比如:

1
2
3
4
5
// 承接上述代码...
Mike.name = "Mike_Zhang";
Alice.name = "Alice_Wang";
Bob.name = "Bob_Li";
System.out.println(Mike.name); // 输出:"Mike_Zhang"

虽然这三个对象都是Person类的实例,但是他们同一个属性是相互独立,不会影响的。比如修改Mike.name,不会影响Alice.nameBob.name

因为每一个实例的属性都是存储在它自己独有的空间内的。


2.2.2 Object Method Invocation

2.2.2 对象方法调用

实例(或对象)的行为(方法)可以通过实例名.方法名(参数)(参数可选) 的形式来调用,比如:

1
2
3
4
// 承接上述代码...
Mike.walk();
Bob.howOld();
Alice.updateName("Alice_Li");

一个类的的每个实例共享了此类的所有的方法,因此实例并不会存储其方法在内存空间中。但是每个实例会根据用户所调用的方法,来选择其对应的行为。


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) 是指把对象的内部属性 和 行为的实现细节 隐藏起来,使得对象的内部属性和行为只能通过公共接口来访问。如图:

Encapsulation - Principles of object-oriented design (Goodrich et al., 2014).

比如

  • 大象装进冰箱只需要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
2
Person studentA = new Student();
Person teacherB = new Teacher();

注:Java允许一个子类的实例赋值给一个其父类类型的变量,称为向上转型

2.并且我们在人类中定义一个方法,来让人类实例介绍自己,代码如下:

1
2
3
4
5
6
public class Person { // 人类
// 省略其他代码
public void introduce() {
System.out.println("I am a person."); // 人类介绍自己为人类
}
}

3.并且分别在人类的子类学生类老师类中定义同名的类似的方法,代码如下:

1
2
3
4
5
6
public class Student extends Person { // 学生类
// 省略其他代码
public void introduce() {
System.out.println("I am a student."); // 学生介绍自己为学生
}
}
1
2
3
4
5
6
public class Teacher extends Person { // 老师类
// 省略其他代码
public void introduce() {
System.out.println("I am a teacher."); // 老师介绍自己为老师
}
}

4.现在我们定义一个舞台,让人类可以介绍自己,代码如下:

1
2
3
4
5
public class Stage {
public void perform(Person person) { // 注意:这里的参数是一个人类的实例
person.introduce(); // 调用人类的介绍方法
}
}

5.现在,邀请学生甲老师乙在舞台上介绍自己:

1
2
3
Stage stage = new Stage();
stage.perform(studentA); // I am a student.
stage.perform(teacherB); // I am a teacher.

可以看到,学生甲老师乙都能具体的介绍自己,不会只说“我是一个人类”,而是说“我是一个学生”或者“我是一个老师”。

而这就是方法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




感谢你的支持

Java面向对象编程详细介绍 | Java OOP Detailed Introduction
https://ultrafish.io/post/Java-oop-detailed-introduction/
Author
Mike_Zhang
Posted on
July 31, 2022
Licensed under