类 与 对象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}
签名为
1max(int, int)
函数重名规则
在一个结构下,不能出现两个函数签名一样的函数
挑战
1 int f(int a){}
以下这些函数,是否可以跟上面的函数同时出现在一个结构里
1 int f(int a, int b){}
可以同时出现
不可以同时出现
1 int f(int b){}
可以同时出现
不可以同时出现
1 int f(double a){}
可以同时出现
不可以同时出现
1 int g(int a){}
可以同时出现
不可以同时出现
1 double f(int a){}
可以同时出现
不可以同时出现
属性, 参数变量 和 局部变量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, long | 0 |
float, double | 0.0 |
boolean | false |
对象类型 | 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 3 4 5 public class 类名 { public 类名() { ... }}
初始化器实质
初始化器 跟 一般方法一样,只不过
跟类名同名
创建对象后立刻被调用
初始化器不是用来 创建/构造 对象
初始化器,是对象创建后 第一个被调用的方法
初始化器的使用案例
使用前
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 外部传入 | 外部 | 对象创建后 |
面向对象 编程方法
编程步骤
数据结构
做设计
设计有哪些类,应该有哪些属性
搭架子
根据设计 填代码
类声明 属性声明
业务逻辑
做设计
针对现有的数据结构,应该有哪些方法
必要时,可能会出现新的类
搭架子
根据设计 写方法声明
填方法
写方法的代码实现
做测试
对每个方法单独进行测试
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)"); }); }}