面向对象思想、类与对象 | JavaSE

面向对象思想、类与对象

一、学习目标

完成本章后,你应该能够:

  • 理解为什么程序规模增大后,仅使用大量独立变量和数组会越来越难以管理。
  • 理解面向对象编程(Object-Oriented Programming,OOP)的基本思想。
  • 能够解释什么是类(Class),什么是对象(Object)。
  • 能够说明类与对象之间“模板与实例”的关系。
  • 能够从现实事物中抽取数据和行为,并设计成一个简单 Java 类。
  • 能够使用 new 创建对象。
  • 能够理解同一个类可以创建多个彼此独立的对象。
  • 能够使用“谁的数据谁存储”的思路完成简单业务建模。
  • 能够区分“类本身”“对象本身”“保存对象引用的变量”三个不同概念。

二、核心知识

2.1 从变量和数组走向对象

在之前的学习中,我们已经可以使用:

变量
数组
二维数组

保存数据。

例如保存一个学生的数据:

String name = "小雨";
double chinese = 95;
double math = 98;

这当然没有问题。

但是如果要保存两个学生:

String name1 = "小雨";
double chinese1 = 95;
double math1 = 98;

String name2 = "小星";
double chinese2 = 88;
double math2 = 92;

人数继续增加以后:

name1
name2
name3
...
chinese1
chinese2
...
math1
math2
...

数据之间的关系开始变得非常混乱。


2.2 使用数组能不能解决

可以继续使用多个平行数组:

String[] names = {
        "小雨",
        "小星",
        "小林"
};

double[] chineseScores = {
        95,
        88,
        90
};

double[] mathScores = {
        98,
        92,
        96
};

这时约定:

names[0]
chineseScores[0]
mathScores[0]

共同表示:

第一个学生。

这种方式可以使用,但随着业务复杂:

姓名
年龄
性别
学号
班级
语文成绩
数学成绩
英语成绩
联系方式
家庭地址
……

你就会得到越来越多互相关联的数组。

真正的问题是:

一个学生的数据本来应该属于同一个整体,却被拆散到了很多变量和数组中。


2.3 面向对象首先解决“数据属于谁”

面向对象思想会问:

这些数据描述的是谁?

例如:

姓名
年龄
语文成绩
数学成绩

描述的是:

学生

那么就应该设计一个能够表示学生的对象。

于是:

学生对象
├── 姓名
├── 年龄
├── 语文成绩
└── 数学成绩

这样:

属于某个学生的数据,就由这个学生对象自己保存。

源课程将这种思想概括为:

万物皆对象,谁的数据谁存储。

作为入门思维,这句话非常有用。


2.4 什么是对象

在当前阶段,可以把对象理解为:

用来表示某一个具体事物的一组数据,以及围绕这些数据可以执行的行为。

例如现实中有一名学生:

姓名:小雨
年龄:21
语文:95
数学:98

程序中就可以创建一个:

Student 对象

来表示这个具体学生。

另一个学生:

姓名:小星
年龄:22
语文:88
数学:92

则可以对应另一个:

Student 对象

因此:

对象代表的是一个具体实例。


2.5 什么是类

问题来了:

Java 怎么知道 Student 对象应该具有姓名、年龄和成绩?

我们需要先设计:

class Student {
}

这就是:

类(Class)。

类用于描述:

这一类对象应该具有什么数据
这一类对象应该具有什么功能

可以把类理解成:

对象的模板
对象的设计图

例如:

class Student {
    String name;
    int age;
    double chinese;
    double math;
}

规定了所有 Student 对象都可以拥有:

name
age
chinese
math

这些数据。


2.6 类与对象的关系

可以使用“设计图与实际产品”来理解。

例如:

汽车设计图
     ↓
生产
     ↓
汽车 A
汽车 B
汽车 C

对应:

Student 类
     ↓
new
     ↓
Student 对象 1
Student 对象 2
Student 对象 3

因此:

类
→ 描述一类对象共同的结构和行为

对象
→ 根据类创建出来的具体实例

2.7 类不是某一个具体对象

例如:

class Star {
    String name;
    int age;
    String gender;
    double height;
    double weight;
}

这个 Star 类本身并不是:

王祖贤
杨幂
赵丽颖

中的任何一个人。

它只是描述:

一个明星对象可以具有哪些数据。

然后才能:

Star s1 = new Star();
Star s2 = new Star();

创建具体对象。


2.8 使用 new 创建对象

最基本格式:

类名 对象变量名 = new 类名();

例如:

Student s1 = new Student();

当前阶段可以拆成:

Student
↓
变量的类型

s1
↓
变量名

new Student()
↓
创建一个 Student 对象

因此:

Student s1 = new Student();

表示:

创建一个 Student 对象,并让变量 s1 保存对这个对象的引用。


2.9 为什么这里不用“保存对象本身”

观察:

Student s1 = new Student();

更加准确的理解不是:

s1 里面塞进了整个 Student 对象

而是:

s1
 │
 │ 引用
 ↓
Student 对象

所以:

s1

通常称为:

引用变量。

引用的内部机制将在下一章重点展开。

当前只需要知道:

变量 s1 可以帮助我们找到并操作对应的 Student 对象。


2.10 一个类可以创建很多对象

例如:

Student s1 = new Student();
Student s2 = new Student();
Student s3 = new Student();

这里创建了:

3 个 Student 对象

而不是:

1 个对象的 3 个名字

可以简单画成:

s1 ──→ Student 对象 A

s2 ──→ Student 对象 B

s3 ──→ Student 对象 C

每一次正常执行:

new Student()

都会创建一个新的 Student 实例。


2.11 多个对象的数据彼此独立

例如:

Student s1 = new Student();
s1.name = "小雨";
s1.age = 21;

Student s2 = new Student();
s2.name = "小星";
s2.age = 22;

可以理解:

s1
 ↓
Student A
├── name = "小雨"
└── age  = 21


s2
 ↓
Student B
├── name = "小星"
└── age  = 22

修改:

s1.age = 23;

不会自动把:

s2.age

也修改成 23

因为:

它们是两个不同对象。


2.12 类中可以描述数据和行为

一个对象不仅可以保存数据,还可以执行与自身有关的操作。

例如:

class Student {
    String name;
    double chinese;
    double math;

    void printTotalScore() {
        System.out.println(
                name + "的总分:"
                        + (chinese + math)
        );
    }

    void printAverageScore() {
        System.out.println(
                name + "的平均分:"
                        + (chinese + math) / 2
        );
    }
}

这里:

name
chinese
math

描述对象的数据。

而:

printTotalScore()
printAverageScore()

描述对象能够执行的行为。


2.13 对象为什么不只是“一张数据表”

源课程为了方便理解,会把对象描述成:

一种特殊的数据结构,可以理解成一张表。

这个比喻适合入门。

但是对象通常不仅包含:

数据

还具有:

行为

例如:

Student
├── 数据
│   ├── name
│   ├── chinese
│   └── math
│
└── 行为
    ├── printTotalScore()
    └── printAverageScore()

因此更加完整的理解应该是:

对象是某种具体事物在程序中的模型,它可以拥有状态,也可以拥有行为。


2.14 面向对象不是“现实世界所有东西都必须建类”

“万物皆对象”是一种帮助初学者建立模型的表达。

真正编程时并不是:

看到一个名词
→ 必须创建一个类

例如:

int count = 10;

没有必要专门设计:

Count 类

面向对象真正重要的是:

当业务中某组数据和行为天然属于一个概念时,把它们组织到对应的对象模型中。


2.15 从现实对象抽取类

假设要开发商城。

现实中:

商品

具有:

商品名称
价格
库存
销量

并具有行为:

展示信息
修改库存
计算销售额

于是可以抽象:

class Product {
    String name;
    double price;
    int stock;
    int sales;

    void printInfo() {
        // ...
    }
}

这就是:

从现实业务概念中提取程序模型。


2.16 从对象到抽象

现实里存在:

张三
李四
王五

每一个具体学生的数据不同。

但我们发现他们具有共同结构:

姓名
年龄
成绩

于是把共同特征提取出来:

Student 类

这种从大量具体事物中寻找共同特征的过程,可以理解为:

抽象(Abstraction)。

因此:

具体学生
↓
寻找共同特征
↓
Student 类

而:

Student 类
↓
new
↓
具体 Student 对象

则是根据抽象模型创建实例。


三、使用方法

3.1 设计明星类

例如:

public class Star {

    String name;
    int age;
    String gender;
    double height;
    double weight;
}

这里描述:

一个明星可以拥有哪些数据

3.2 创建第一个对象

public class StarTest {

    public static void main(String[] args) {

        Star s1 = new Star();

        s1.name = "王祖贤";
        s1.age = 50;
        s1.gender = "女";
        s1.height = 172.5;
        s1.weight = 50.5;

        System.out.println(s1.name);
        System.out.println(s1.age);
        System.out.println(s1.gender);
        System.out.println(s1.height);
        System.out.println(s1.weight);
    }
}

执行过程可以抽象为:

设计 Star 类
 ↓
new Star()
 ↓
创建 Star 对象
 ↓
s1 引用对象
 ↓
向对象中保存数据

3.3 创建第二个对象

Star s2 = new Star();

s2.name = "杨幂";
s2.age = 38;
s2.gender = "女";
s2.height = 171.5;
s2.weight = 52.5;

这时:

s1
↓
对象 A


s2
↓
对象 B

两个对象互相独立。


3.4 Student 案例

定义:

public class Student {

    String name;
    double chinese;
    double math;

    public void printTotalScore() {
        System.out.println(
                name + "同学总分:"
                        + (chinese + math)
        );
    }

    public void printAverageScore() {
        System.out.println(
                name + "同学平均分:"
                        + (chinese + math) / 2
        );
    }
}

创建对象:

public class StudentTest {

    public static void main(String[] args) {

        Student s1 = new Student();

        s1.name = "小雨";
        s1.chinese = 100;
        s1.math = 100;

        s1.printTotalScore();
        s1.printAverageScore();


        Student s2 = new Student();

        s2.name = "小星";
        s2.chinese = 59;
        s2.math = 99;

        s2.printTotalScore();
        s2.printAverageScore();
    }
}

这里已经形成一个完整的对象模型:

学生的数据
+
处理学生自己数据的方法

3.5 设计 Car 类

在写代码之前先分析:

汽车有什么数据?

品牌
颜色
价格
速度

再分析:

汽车能够做什么?

启动
行驶
停车
展示信息

于是:

class Car {

    String brand;
    String color;
    double price;
    double speed;

    void start() {
        System.out.println("汽车启动");
    }

    void stop() {
        System.out.println("汽车停车");
    }
}

这才是更重要的能力:

先设计对象,再写代码。


3.6 对象建模的基本步骤

以后遇到简单面向对象题,可以按照:

第一步:找对象
↓
系统中有哪些重要事物?

第二步:找数据
↓
这个对象应该保存哪些信息?

第三步:找行为
↓
这个对象应该完成哪些操作?

第四步:设计类
↓
使用类描述共同结构

第五步:new 对象
↓
创建具体实例并使用

四、原理与进阶

4.1 Java 中对象的规范含义

从 Java 语言规范角度:

类实例和数组都属于对象。

因此之前学习:

int[] arr = new int[5];

实际上就已经使用过对象。

数组属于对象,只不过:

数组对象

是 Java 提供的一种特殊结构。

而现在学习的是:

我们自己声明类
↓
创建类实例对象

4.2 new Student() 到底产生什么

对于:

new Student()

运行时会创建一个新的 Student 类实例。

例如:

Student s1 = new Student();
Student s2 = new Student();

两次 new Student() 正常情况下对应两个不同的新对象。

因此:

new

可以理解成:

创建对象的关键入口。

关于对象创建过程中构造器到底做什么,下一章会专门学习。


4.3 类、对象和引用变量必须分开

这是整个面向对象阶段最关键的基础之一。

Student s1 = new Student();

这里有三个概念:

Student
↓
类 / 类型


new Student()
↓
新创建的对象


s1
↓
保存对象引用的变量

不要把三者全部混叫成:

Student

以后学习:

参数传递
null
多态
集合
对象数组
GC

时都依赖这个区分。


4.4 类负责描述共同结构,对象负责保存个体状态

例如:

class Student {
    String name;
    int age;
}

类描述:

Student 都有 name
Student 都有 age

但:

Student s1 = new Student();
Student s2 = new Student();

不同对象可以保存:

s1
name = 小雨
age = 21


s2
name = 小星
age = 22

所以:

类
→ 共同结构

对象
→ 个体状态

4.5 面向对象与面向过程不是非黑即白

即使是面向对象程序:

void printTotalScore() {
    double total = chinese + math;
    System.out.println(total);
}

方法内部仍然存在:

顺序
分支
循环
算法

所以:

面向对象不是把之前学过的语法全部推翻。

而是增加一个更高层次的组织方式:

基础语法
↓
方法
↓
对象
↓
类与对象协作
↓
更大型的软件结构

五、实践应用

现实 Java 项目中到处都是对象。

例如电商:

User
Product
Order
OrderItem
Address
Coupon

博客系统:

User
Article
Tag
Comment
Tutorial
Chapter

学校系统:

Student
Teacher
Course
Classroom
Score

银行:

Account
Customer
Card
Transaction

这些类背后的核心仍然是:

找到业务对象
↓
描述对象的数据
↓
描述对象的行为
↓
让对象之间协作

因此,面向对象不是为了:

多写几个 class。

而是为了:

用与业务更加接近的模型组织程序。


六、常见问题

6.1 类和对象是不是一回事?

不是。

类
→ 模板、设计图

对象
→ 根据类创建出来的具体实例

6.2 一个类只能创建一个对象吗?

不是。

一个普通类通常可以创建多个对象:

Student s1 = new Student();
Student s2 = new Student();
Student s3 = new Student();

6.3 Student s1 已经创建对象了吗?

没有。

单独:

Student s1;

只是声明一个 Student 类型引用变量。

真正创建 Student 实例通常需要:

new Student()

6.4 new Student()s1 是一回事吗?

不是。

new Student()
→ 创建对象

s1
→ 保存对象引用的变量

6.5 修改 s1 会不会影响 s2?

如果:

Student s1 = new Student();
Student s2 = new Student();

它们指向不同对象,那么修改一个对象的数据不会自动修改另一个对象。

但如果两个变量后来指向同一个对象,情况就不同。

这个问题将在下一章展开。


6.6 面向对象是不是必须模拟现实世界所有细节?

不是。

软件模型只应该保留:

当前业务真正关心的数据和行为。

设计学生选课系统时可能需要:

学号
姓名
专业
课程

未必需要:

鞋码
头发长度
今天早餐

这就是抽象和建模。


6.7 类是不是只能包含变量?

不是。

类通常可以描述:

数据
+
行为

在当前阶段主要对应:

成员变量
成员方法

下一章会正式学习。


6.8 为什么不直接把所有代码都写 main?

小程序当然可以。

但规模增大后,全部塞进 main 会导致:

数据关系混乱
功能职责混乱
重复代码增加
难以维护

类和对象可以帮助程序逐步形成更加清晰的结构。


七、练习与验收

7.1 知识问答

  1. 什么是面向对象编程?
  2. 什么是对象?
  3. 什么是类?
  4. 类为什么可以理解成对象的模板或设计图?
  5. 类与对象之间是什么关系?
  6. new 在创建对象过程中承担什么作用?
  7. Student s1 = new Student(); 中三个部分分别表示什么?
  8. 一个类能否创建多个对象?
  9. 多个独立对象的成员数据是否互相独立?
  10. “万物皆对象,谁的数据谁存储”应该如何理解?
  11. 对象与普通的一组独立变量相比有什么组织优势?
  12. 为什么建模时不能把现实事物所有信息都放进类?
  13. 什么是抽象?
  14. 类、对象、引用变量三个概念有什么区别?

7.2 代码阅读

禁止运行:

class Student {
    String name;
    int age;
}

public class OopRead01 {

    public static void main(String[] args) {

        Student s1 = new Student();
        Student s2 = new Student();

        s1.name = "小雨";
        s1.age = 21;

        s2.name = "小星";
        s2.age = 22;

        System.out.println(s1.name);
        System.out.println(s2.age);
    }
}

要求:

  1. 一共创建了几个 Student 对象?
  2. s1s2 各自是什么?
  3. 画出类、对象、引用变量之间的关系。
  4. 修改 s1.age 是否会直接改变 s2.age

7.3 手写代码

  1. 设计 Star 类,至少包含姓名、年龄、性别、身高、体重五个数据。
  2. 创建两个不同的 Star 对象并分别保存数据。
  3. 设计 Book 类,至少 3 个数据和 2 个行为。
  4. 设计 Product 类,至少 3 个数据和 2 个行为。
  5. 设计 Car 类,至少 3 个数据和 2 个行为。
  6. 设计 User 类,至少 3 个数据和 2 个行为。
  7. 为学生设计 Student 类,并创建两个学生对象。

7.4 Debug

下面代码的设计存在问题:

String[] names = {"小雨", "小星"};
double[] chinese = {90, 80};
double[] math = {95, 88};
int[] ages = {21, 22};

需求不断增加,还要继续加入:

英语成绩
学号
手机号
班级
地址

要求:

  1. 说明平行数组方案会产生什么维护问题。
  2. 使用面向对象思想重新设计。
  3. 明确哪些数据应该属于 Student 对象。

7.5 综合训练

将下面的业务建模成对象:

一个博客系统需要管理文章。每篇文章包含标题、摘要、正文、浏览量和发布时间,并具有“展示基本信息”和“增加浏览量”等行为。

要求:

  1. 先写自然语言模型。
  2. 找出对象。
  3. 找出数据。
  4. 找出行为。
  5. 设计 Article 类。
  6. 创建两个 Article 对象。
  7. 不要求使用构造器、private、getter/setter。

7.6 本章验收

关闭资料后确认:

  • [ ] 能独立解释面向对象思想。
  • [ ] 能区分类与对象。
  • [ ] 能从现实业务中找出候选对象。
  • [ ] 能从对象中提取数据和行为。
  • [ ] 能从零定义一个简单类。
  • [ ] 能使用 new 创建对象。
  • [ ] 能解释 Student s = new Student();
  • [ ] 能创建同一类的多个对象。
  • [ ] 能画出类、引用变量与对象之间的基本关系。
  • [ ] 不会把类、对象、对象变量三个概念混成一个概念。

如果面对一个新的业务对象时,只会照抄:

class Student {
}

却无法回答:

这个类为什么存在?
它代表谁?
应该保存什么数据?
应该提供什么行为?

那么你还没有真正建立面向对象思维。