类 与 对象Classes and Objects

描述现实

用于找到现实和代码中的映射关系, 从而更快的写代码

名词

名词意义
类 Class类型 名词
对象 Object / 实例 Instance一个具体的东西
属性 Property / 变量 Variable / 字段 Field一个东西的信息
方法 Method / 函数 Function / 消息 Message用于操作一个东西

灯, 这盏灯, 亮度, 打开

关系

类 与 对象

要想创建一个对象, 先要设计一个类

对象 与 属性

每个对象的属性可以有不同的值

类 与 属性

一个类有属性,暗指每个对象都有属性

类其实本身并没有属性

方法 与 属性

方法用于操作对象属性

类 与 方法

一个类有方法,暗指每个对象都能执行这个操作

类其实本身并无法执行操作

对象 与 属性Objects and Properties

结构化

什么里面有什么

文件管理器

以前 : 文件都放到一起

现在 : 文件放在对应文件夹里, 要一层层的找

封装数据

使用前

Game.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
public class Game { public static void main(String[] args) { new Game().run(); } public void run(){ int atk1; int def1; int hp1; int atk2; int def2; int hp2; atk1 = 100; def1 = 100; hp1 = 100; atk2 = 100; def2 = 100; hp2 = 100; }}
使用后

Player.java

1 2 3 4 5
public class Player { public int atk; public int def; public int hp;}

Game.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
public class Game { public static void main(String[] args) { new Game().run(); } public void run(){ Player player1 = new Player(); Player player2 = new Player(); player1.atk = 100; player1.def = 100; player1.hp = 100; player2.atk = 100; player2.def = 100; player2.hp = 100; }}

封装模型

设计数据

对象

根据类声明,创建一套数据格

类 与 属性

类中的属性,是为对象设计的,类并没有属性

对象 与 属性

每个对象有独立的属性,互不干扰

类声明

结构
类 {

}
模板
public class 类名 {

}
限制

一个文件里只能有一个类*

类名必须和文件名一致

命名规范

所有单词首字母大写, 其它字母小写

属性声明

结构
类 {
    属性
}
模板
public class 类名 {
    public 属性类型 属性名;
}
限制

同类下属性不能重名

不同类下,可以起同样的名字

命名规范

和变量命名一致

创建对象

类名 即 变量类型

1
类名 对象变量 = new 类名();Player player = new Player();

使用属性

就跟使用一般变量一样, 但得通过对象寻找

1 2
对象变量.属性名 = 2;Console.println(对象变量.属性名);player.atk = 2;Console.println(player.atk);

方法Methods

封装方法

使用前

Player.java

1 2 3 4 5
public class Player { public int atk; public int def; public int hp;}

Game.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
public class Game { public static void main(String[] args) { new Game().run(); } public void run(){ Player player1 = new Player(); Player player2 = new Player(); player1.atk = 100; player1.def = 100; player1.hp = 100; player2.atk = 100; player2.def = 100; player2.hp = 100; Console.println("atk:" + player1.atk + ", def:" + player1.def); Console.println("atk:" + player2.atk + ", def:" + player2.def); }}
使用后

Player.java

1 2 3 4 5 6+7+8+9
public class Player { public int atk; public int def; public int hp; public void trace() { Console.println("atk:" + this.atk + ", def:" + this.def); }}

Game.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17*18*19 20
public class Game { public static void main(String[] args) { new Game().run(); } public void run(){ Player player1 = new Player(); Player player2 = new Player(); player1.atk = 100; player1.def = 100; player1.hp = 100; player2.atk = 100; player2.def = 100; player2.hp = 100; player1.trace(); player2.trace(); }}

封装模型

类 与 方法

类中定义的方法,是给将来造出的对象使用的

对象 与 方法

执行方法时,必须有对象在前面做引导

方法声明

结构
类 {
    属性
    方法 {
        执行代码
    }
}
模板
public class 类名 {
    public 属性类型 属性名;
    public 返回值类型 方法名(参数1类型 参数1名, ...) {
        执行代码
    }
}

方法调用

就跟使用一般函数一样, 但得通过对象变量引导

1
对象变量.方法名(参数1, ...);player1.trace();

方法基于对象

方法和一般函数最大的差别

方法是基于对象的

this

this 用于找到在调用方法时,希望操作的对象

方法 与 属性

执行方法的时候,this 的属性 就是 被调用对象的属性

单一原则

人力与公司 的 故事

以前 : 一个人什么都干

现在 : 扫地的专门扫地, 算账的专门算账, 销售的专门卖东西

各执其职, 各自专精, 保持沟通

方法 调用 方法

方法可以直接调用同级别方法

1 2 3 4 5 6 7*8 9 10+11+12+13
public class Player { public int atk; public int def; public int hp; public void trace(){ Console.println(this.description()); } public String description() { return "atk:" + this.atk + ", def:" + this.def; }}
this 传递

this 在调用时 可以在方法间传递

从而代表 都是操作同一个对象,以及同一个对象的属性

this 可省略

在 java 中 可以省略不写 this

编译时会自动补上

1 2 3 4 5 6 7*8 9 10 11*12 13
public class Player { public int atk; public int def; public int hp; public void trace(){ Console.println(description()); } public String description() { return "atk:" + atk + ", def:" + def; }}

方法重载Method Overloading

能干什么

同一个类下,可以有同名方法

函数签名

函数签名 = 函数名 + 参数类型列表

案例
1 2 3
public int max(int a, int b) { return 0}

签名为

1
max(int, int)

函数重名规则

在一个结构下,不能出现两个函数签名一样的函数

挑战
1
int f(int a){}

以下这些函数,是否可以跟上面的函数同时出现在一个结构里

1.
1
int f(int a, int b){}
A

可以同时出现

B

不可以同时出现

2.
1
int f(int b){}
A

可以同时出现

B

不可以同时出现

3.
1
int f(double a){}
A

可以同时出现

B

不可以同时出现

4.
1
int g(int a){}
A

可以同时出现

B

不可以同时出现

5.
1
double f(int a){}
A

可以同时出现

B

不可以同时出现

属性, 参数变量 和 局部变量Properties, Parameters and Local Variables

变量的种类

属性, 参数变量, 局部变量

1 2 3 4 5 6 7 8 9
public class Var { public int a; public void f(int b){ // ... int c; // ... }}

变量的作用域与生命周期

种类生命周期作用域
属性随对象生, 随对象死所有方法均可访问
参数变量方法调用时生, 调用完死在声明的方法内可以访问
局部变量变量声明时生, 碰到包含它的最近的大括号时死从声明的那一行, 到包含它的最近的大括号结束

变量命名问题

参数和局部变量 可以跟 属性重名

其它组合 均不可以

属性默认值

新建的对象如果没有给属性赋值,属性会变为默认值

类型默认值
byte, short, char, int, long0
float, double0.0
booleanfalse
对象类型null

属性 与 局部变量 或者 参数 同名

使用 this 区分 属性 和 局部变量,此时 this 不能省略

1 2 3 4 5 6 <7 8 9 <10 11
public class Cla { public int a; public void func() { int a = 3; // 完整写法,属性 Console.println(this.a); 0 // 不能省略,省略后是局部变量 Console.println(a); 3 }}

就近原则

Getters and SettersAccessors and Mutators

案例

Player.java

1 2 3 4 5
public class Player { public int atk; public int def; public int hp;}

Game.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
public class Game { public static void main(String[] args) { new Game().run(); } public void run(){ Player player1 = new Player(); Player player2 = new Player(); player1.atk = 100; player1.def = 100; player1.hp = 100; player2.atk = 100; player2.def = 100; player2.hp = 100; int damage = 200; player1.hp -= damage; player2.hp -= damage; }}
思考

上面的代码 违背了什么业务逻辑?

对 hp 进行保护

Game.java

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 27
public class Game { public static void main(String[] args) { new Game().run(); } public void run(){ Player player1 = new Player(); Player player2 = new Player(); player1.atk = 100; player1.def = 100; player1.hp = 100; player2.atk = 100; player2.def = 100; player2.hp = 100; int damage = 200; player1.hp -= damage; if (player1.hp < 0) { player1.hp = 0; } player2.hp -= damage; if (player2.hp < 0) { player2.hp = 0; } }}
思考

上面的代码 可以如何改进

增加 Setter

Player.java

1 2 3 4 5 6+7+8+9+10+11+12
public class Player { public int atk; public int def; public int hp; public void setHp(int hp){ if (hp < 0) { hp = 0; } this.hp = hp; }}

Game.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18*19*20 21
public class Game { public static void main(String[] args) { new Game().run(); } public void run(){ Player player1 = new Player(); Player player2 = new Player(); player1.atk = 100; player1.def = 100; player1.hp = 100; player2.atk = 100; player2.def = 100; player2.hp = 100; int damage = 200; player1.setHp(player1.hp - damage); player2.setHp(player2.hp - damage); }}

Getter Setter

什么是

针对属性的访问和赋值的专用方法(一套)

Setter

用于赋值

1 2 3 4 5
// 使用前player.hp = 200; // 使用后 player.setHp(200);
Getter

用于取值

1 2 3 4 5
// 使用前int value = player.hp; // 使用后int value = player.getHp();
固定格式
1 2 3 4 5 6 7 8 9
// Getterpublic 属性类型作为返回值类型 getXxxxXxx(){ return xxxxXxx;} // Setterpublic void setXxxxXxx(属性类型作为参数类型 xxxxXxx){ this.xxxxXxx = xxxxXxx;}

具体实现代码可以产生差异,但方法签名必须符合标准

隐藏属性

Player.java

1 2*3*4*5 6 7
public class Player { private int atk; private int def; private int hp; ...}
效果

隐藏后,外界(非 Player 类内)无法直接使用该属性

Player.java

1 2 3 4 5 6 7 8 9 10!11!12!13!14!15!16 17 18!19!20 21
public class Game { public static void main(String[] args) { new Game().run(); } public void run(){ Player player1 = new Player(); Player player2 = new Player(); player1.atk = 100; player1.def = 100; player1.hp = 100; player2.atk = 100; player2.def = 100; player2.hp = 100; int damage = 200; player1.setHp(player1.hp - damage); player2.setHp(player2.hp - damage); }}

使用会报错

封装Encapsulation

是什么

对部分资源进行隐藏保护

公开想公开的,隐藏不想公开的

同时对资源进行结构化打包

封装模型
暴露属性
保护属性
代码结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
public class 类名 { private 属性类型 属性名; public 属性类型作为返回值类型 getXxxxXxx(){ return xxxxXxx; } public void setXxxxXxx(属性类型作为参数类型 xxxxXxx){ this.xxxxXxx = xxxxXxx; } public 返回值类型 一般方法名(参数1类型 参数1名, ...) { 执行代码 }}

初始化器Constructors

初始化器 / 构造方法 / 构造函数 / 构造器

对象创建实质

1
new 类名();

实质上可以理解为

1
(new 类名).类名();
详细步骤
1.

创建对象,根据类里的属性声明,开辟数据格

2.

立刻调用与类名同名的方法,可以作为初始化的时机

填补实现
1 2 3 4 5
public class 类名 { public 类名() { ... }}

初始化器实质

初始化器 跟 一般方法一样,只不过

1.

跟类名同名

2.

创建对象后立刻被调用

初始化器不是用来 创建/构造 对象

初始化器,是对象创建后 第一个被调用的方法

初始化器的使用案例

使用前

Player.java

1 2 3 4 5 6 7 8 9 10 11 12 13
public class Player { private int atk; private int def; private int hp; public void trace(){ Console.println("atk:" + atk + ", def:" + def); } // Getters & Setters ... }

Game.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
public class Game { public static void main(String[] args) { new Game().run(); } public void run(){ Player player1 = new Player(); Player player2 = new Player(); player1.setAtk(100); player1.setDef(100); player1.setHp(100); player2.setAtk(100); player2.setDef(100); player2.setHp(100); int damage = 200; player1.setHp(player1.getHp() - damage); player2.setHp(player2.getHp() - damage); }}
使用后

Player.java

1 2 3 4 5 6+7+8+9+10+11 12 13 14 15 16 17 18 19
public class Player { private int atk; private int def; private int hp; public Player(){ atk = 100; def = 100; hp = 100; } public void trace(){ Console.println("atk:" + atk + ", def:" + def); } // Getters & Setters ... }

Game.java

1 2 3 4 5 6 7 8 9 10-11-12-13-14-15-16 17 18 19 20 21
public class Game { public static void main(String[] args) { new Game().run(); } public void run(){ Player player1 = new Player(); Player player2 = new Player(); player1.setAtk(100); player1.setDef(100); player1.setHp(100); player2.setAtk(100); player2.setDef(100); player2.setHp(100); int damage = 200; player1.setHp(player1.getHp() - damage); player2.setHp(player2.getHp() - damage); }}

有参初始化

利用方法重载, 构造方法也可以方法重载

Player.java

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
public class Player { private int atk; private int def; private int hp; public Player(){ atk = 100; def = 100; hp = 100; } public Player(int atk, int def, int hp) { this.atk = atk; this.def = def; this.hp = hp; } public void trace(){ Console.println("atk:" + atk + ", def:" + def); } // Getters & Setters ... }

Game.java

1 2 3 4 5 6 7 8*9 10 11 12 13 14
public class Game { public static void main(String[] args) { new Game().run(); } public void run(){ Player player1 = new Player(); Player player2 = new Player(200, 150, 150); int damage = 200; player1.setHp(player1.getHp() - damage); player2.setHp(player2.getHp() - damage); }}

初始化器之间调用

案例

Player.java

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
public class Player { private int atk; private int def; private int hp; public Player(){ atk = 100; def = 100; hp = 100; } public Player(int atk, int def, int hp) { this.atk = atk; this.def = def; this.hp = hp; } public void trace(){ Console.println("atk:" + atk + ", def:" + def); } // Getters & Setters ... }
优化后

使用 this() 可以在 一个初始化器内 调用 另外一个初始化器

Player.java

1 2 3 4 5 6 7*8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
public class Player { private int atk; private int def; private int hp; public Player(){ this(100, 100, 100) } public Player(int atk, int def, int hp) { this.atk = atk; this.def = def; this.hp = hp; } public void trace(){ Console.println("atk:" + atk + ", def:" + def); } // Getters & Setters ... }

初始化器 调用 一般方法

初始化器 可以调用 一般方法 协助初始化

但一般方法 不能调用初始化器

Player.java

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 27
public class Player { private int atk; private int def; private int hp; public Player(){ initialize(100, 100, 100) } public Player(int atk, int def, int hp) { initialize(atk, def, hp); } public void initialize(int atk, int def, int hp) { this.atk = atk; this.def = def; this.hp = hp; } public void trace(){ Console.println("atk:" + atk + ", def:" + def); } // Getters & Setters ... }

初始化方法 自动生成规则

无参自动生成

如果不写任何构造方法

系统自动生成一个无参构造

写了就不生成

如果写了任何构造方法

系统就不自动生成无参构造

这时, 一般会手动补一个无参构造

初始化块Instance Initialization Blocks

运行时机:对象创建后,任何初始化器调用前

Cla.java

1 2 3 4 5 6 7 8 9 10 11
public class Cla { { Console.println("Instance Initialization Block"); } Cla () { Console.println("Constructor 1"); } Cla (int a) { Console.println("Constructor 2"); }}

Driver.java

1 2 3 4 5 6
public class Driver { public static void main(String[] args) { new Cla(); new Cla(2); }}

你会看到

< < < <
Instance Initialization BlockConstructor 1Instance Initialization BlockConstructor 2

属性设置 常用版型

使用属性默认值

属性有默认值,如果不赋值,就是默认值

如果默认值就是想要的,那无需设置

Player.java

1 2 3 4
public class Player { private int experiencePoint; ...}

Game.java

1 2 3 4 5 6 7 8
public class Game { ... public void run() { Player player = new Player(); Console.println(player.getExperiencePoint()); }}

属性声明直接初始化

属性声明的位置可以直接赋值,

每个新创建的对象,在创建对象分配数据格的时候,属性立刻被赋值

Player.java

1 2 3 4
public class Player { private int coins = 100; ...}

Game.java

1 2 3 4 5 6 7 8
public class Game { ... public void run() { Player player = new Player(); Console.println(player.getCoins()); }}

初始化器内定死

跟 属性声明直接初始化 一样

时间点稍微靠后一点点

每个新创建的对象,在调用初始化方法时,属性被赋值

Player.java

1 2 3 4 5 6 7 8
public class Player { private int coins; public Player() { coins = 100; } ...}

Game.java

1 2 3 4 5 6 7 8
public class Game { ... public void run() { Player player = new Player(); Console.println(player.getCoins()); }}

初始化器外部传入

时间点依然很靠前,但是对象自己无法决定这个值

Chart.java

1 2 3 4 5 6 7 8
public class Chart { private int year; public Chart(int year) { this.year = year; } ...}

Report.java

1 2 3 4 5 6 7 8
public class Report { ... public void run() { Chart chart = new Chart(9917); Console.println(chart.getYear()); }}

Setter 外部传入

在对象创建后,初始化器也结束后,后期可以在任何时候 改变属性的值

User.java

1 2 3 4
public class User { private String email; ...}

UserView.java

1 2 3 4 5 6 7 8 9 10
public class UserView { ... public void run() { User user = new User(); Console.println(user.getEmail()); user.setEmail("not-found@zzax.io"); Console.println(user.getEmail()); }}

对比

方法数值决定权赋值时间点
使用属性默认值自己对象创建时
属性声明直接初始化自己对象创建时
初始化器内定死自己对象创建时
初始化器外部传入外部对象创建时
Setter 外部传入外部对象创建后

面向对象 编程方法

编程步骤

数据结构
1.

做设计

设计有哪些类,应该有哪些属性

2.

搭架子

根据设计 填代码

类声明 属性声明

业务逻辑
1.

做设计

针对现有的数据结构,应该有哪些方法

必要时,可能会出现新的类

2.

搭架子

根据设计 写方法声明

3.

填方法

写方法的代码实现

4.

做测试

对每个方法单独进行测试

TDD

测试驱动开发 Test-driven Development

在 TDD 的理念中,会先进行 做测试,再做填方法

数据结构

设计

Point 基础版

Q36 E01

搭架子

类声明

属性声明

getter setter

业务逻辑

设计
1
public int distanceToOrigin()
搭架子

根据方法签名,写出通过语法检查的方法。

需要返回值的地方,先返回一个默认值。

1 2 3 4 5 6
public class Point { ... public int distanceToOrigin() { return 0; }}
填方法
资源分析

属性

参数

局部变量

业务分析

要干什么?

产出什么?返回值

会改什么?属性是否会被更改

写代码
1 2 3 4*5 6
public class Point { ... public int distanceToOrigin() { return Math.sqrt(x * x, y * y); }}

单元测试Unit Testing

目的

确保模块的每个组件都是健康且正确的

三大步骤

准备:对象,参数

调用

检查:返回值,对象状态

测试有返回值的方法

有些方法会 会有返回值

需要检测 返回值是否正确

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public class Driver { public static void main() { new Driver().run(); } public void run() { // 1. 准备 Point point = new Point(); point.setX(3); point.setY(4); // 2. 调用 String value = point.description(); // 3. 检查 Console.println(value); }}

测试 修改属性 的方法

有些方法会更改对象状态(属性的值)

需要检测 属性是否正确

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
public class Driver { public static void main() { new Driver().run(); } public void run() { // 1. 准备 Point point = new Point(3, 4); // 2. 调用 point.flip(); // 3. 检查 Console.println(point.getX()); Console.println(point.getY()); // 3+. or 借用安全的方法 检查 Console.println(point.description()); }}

原则

隔离

两个测试之间,尽量隔离开

1 2 3 4 5 6 7 8 9 10 11 12 13 14
public class Driver { public static void main() { new Driver().run(); } public void run() { { ... } { ... } }}
单一不可信

测试时,一个测试区域的代码,尽量做到只有一个是不可信的,

其余的都是可信,或者在之前的测试中,有测试过的

尽量不要将多个不可信元素,混合在一起测试(这么做就不是单元测试了)

测试构造方法

需要确定调用后 对象的属性 是否正常

1 2 3 4 5 6 7 8 9 10 11 12 13 14
public class Driver { public static void main() { new Driver().run(); } public void run() { // 1 + 2. 准备 + 调用 Point point = new Point("3,4"); // 3. 检查 Console.println(point.getX()); Console.println(point.getY()); }}

使用 Jadeite 帮助测试

功能
1 2 3 4 5 6 7 8 9
public class Driver { public void run() { Checker.task(<测试名>, task -> { ... task.check(<检查名>, <当前值>, <期待值>); }); ... }}
样例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
public class Driver { public static void main() { new Driver().run(); } public void run() { Checker.task("description", task -> { Point point = new Point(); point.setX(3); point.setY(4); String value = point.description(); task.check("value", value, "(3, 4)"); }); Checker.task("flip", task -> { Point point = new Point(3, 4); point.flip(); task.check("description", point.description(), "(-3, -4)"); }); }}

ZZAX 微信公众

文档一更新,立刻告诉你