AP CSA 3.6:用 BlueJ 看懂对象引用
3.6 最需要建立的是一个清楚的 reference mental model: object 在哪里?reference 指向哪里?
① 现在一共有几个 object? ② 哪些 reference 指向同一个 object?
先看整条路线
点击任意卡片,直接展开对应部分。
new1 Class 怎么会变成一种 Type?
先看三行代码:
private int level;
private String username;
private Avatar avatar;
第三行中的 Avatar 出现在了 type 的位置。
原因是 Java 中:
当我们写:
public class Avatar
{
...
}
从这一刻开始,Avatar 就可以像 String 一样出现在
variable declaration、parameter 和 return type 中。
| 代码 | Type | Variable 中保存什么 |
|---|---|---|
int level; |
primitive type | 一个整数值 |
String username; |
class / reference type | 一个 object reference |
Avatar avatar; |
我们定义的 class / reference type | 一个 object reference |
avatar
│
│ reference
▼
┌────────────────┐
│ Avatar object │
└────────────────┘
String 和 Avatar 有什么共同点?
★ BlueJ 准备:建立 Avatar.java
新建 BlueJ Project,例如:
Unit3_6_ObjectReferences
创建 class:Avatar,然后复制下面的完整代码。
public class Avatar
{
private String name;
private int health;
public Avatar(String n, int h)
{
name = n;
health = h;
}
public String getName()
{
return name;
}
public int getHealth()
{
return health;
}
public void setHealth(int h)
{
health = h;
}
public void heal(int amount)
{
health += amount;
}
public void takeDamage(int amount)
{
health -= amount;
}
public String toString()
{
return name + " (HP: " + health + ")";
}
}
Compile 后,在 Object Bench 创建:
new Avatar("Knight", 80)
object name 设为:
avatar1
右键 avatar1 → Inspect,把它想成:
avatar1
│
▼
┌─────────────────┐
│ Avatar object │
│ name = Knight │
│ health = 80 │
└─────────────────┘
2 BlueJ Task 1:让 GameProfile 拥有一个 Avatar
创建第二个 class:GameProfile。
复制下面的 scaffold,再完成其中的空格。
public class GameProfile
{
private String username;
private int level;
// TODO 1
// GameProfile has an Avatar.
// 这里的 type 应该是什么?
private ________ avatar;
// TODO 2
// constructor 需要接收一个 Avatar object 的 reference。
public GameProfile(String u, int l, ________ a)
{
username = u;
level = l;
// 把 parameter 中的 reference 存进 instance variable。
avatar = ________;
}
public void printProfile()
{
System.out.println("Username: " + username);
System.out.println("Level: " + level);
System.out.println("Avatar: " + avatar);
}
// TODO 3
// 接收另一个 Avatar reference,然后让那个 Avatar 恢复 health。
public void healAvatar(________ target, int amount)
{
________.heal(amount);
}
// TODO 4
// 返回这个 GameProfile 保存的 Avatar reference。
public ________ getAvatar()
{
return ________;
}
}
先完成 TODO 1 和 TODO 2,然后 Compile。
为什么
String 可以放在 type 的位置?为什么
Avatar 也可以放在 type 的位置?
String
Java 已经提供的 class。
String username;
Avatar
我们自己写的 class。
Avatar avatar;
这时可以用一句很简单的话描述:
这就是这里需要理解的 has-a relationship。
4 BlueJ Task 2:Object 也可以作为 Method Argument
现在完成 scaffold 中的 TODO 3:
public void healAvatar(________ target, int amount)
{
________.heal(amount);
}
Compile 后,在 BlueJ 中调用:
profile1.healAvatar(avatar1, 30)
avatar1 的 health 会不会改变?
method 运行时,target 是一个新的 parameter variable。
它得到的是 reference value 的 copy:
avatar1 ─────┐
▼
Avatar
▲
target ──────┘
target.heal(amount) 改变的是 parameter variable 本身,
还是它所指向的 Avatar object?
实验完成后,再调用:
profile1.printProfile()
5 Experiment:修改 Object vs 修改 Reference
在 GameProfile 中加入下面这个 method,并补全 type:
public void tryToReplace(________ target)
{
target = new Avatar("Robot", 100);
}
Compile 后调用:
profile1.tryToReplace(avatar1)
avatar1 会指向 Knight 还是 Robot?
method 开始时:
avatar1 ─────┐
▼
Knight
▲
target ──────┘
执行:
target = new Avatar("Robot", 100);
完成这张图:
avatar1 ─────────► __________
target ─────────► __________
然后在 BlueJ 中依次:
- 调用
tryToReplace - Inspect
avatar1 - 调用
profile1.printProfile()
把两种操作并排看
操作 A
target.heal(30);
想一想:谁发生了变化?
操作 B
target = new Avatar("Robot", 100);
想一想:谁发生了变化?
target.heal(30) → 修改 ____________________target = new Avatar(...) → 修改 ____________________
6 BlueJ Task 3:Method 返回一个 Object
现在完成 scaffold 中的 TODO 4:
public ________ getAvatar()
{
return ________;
}
Avatar object 的 reference,
return type 应该是什么?
Compile 后:
- 右键
profile1 - 调用
getAvatar() - 让 BlueJ 显示返回的 object
- 如果 BlueJ 提供 Get,把返回的 object 放到 Object Bench
- 给它一个名字,例如
avatar2
现在 Object Bench 上可能有:
avatar1
profile1
avatar2
用
new Avatar(...) 实际执行了几次来判断。
如果 avatar2 和 profile1.avatar 指向同一个 object:
avatar2.setHealth(1)
然后再次调用:
profile1.printProfile()
观察结果,再画出三个 reference 的箭头。
✓ AP 题怎么追踪 Reference?
代码变长以后,继续使用同一套方法。
第一步:数 new
Avatar a1 = new Avatar("Knight", 80);
Avatar a2 = a1;
Avatar a3 = a2;
这里真正执行的 new Avatar(...) 只有一次。
第二步:数 object
1 个 Avatar object
第三步:画 reference
a1 ───┐
a2 ───┼──► Avatar object
a3 ───┘
Primitive 和 Object 放在一起比较
Primitive argument
score = 80
│ copy
▼
x = 80
复制的是 primitive value。
Object argument
avatar1 ───┐
▼
Avatar
▲
target ────┘
复制的是 reference value。
区别只是被复制的 value 是 primitive value,还是 reference value。
⚡ BlueJ Challenge:先画,再运行
Challenge 1
Avatar a1 = new Avatar("Knight", 80);
Avatar a2 = a1;
a2.setHealth(10);
a1.getHealth() 是多少?
Challenge 2
public void change(Avatar target)
{
target.setHealth(5);
}
Avatar a1 = new Avatar("Knight", 80);
profile1.change(a1);
a1.getHealth() 是多少?
Challenge 3
public void change(Avatar target)
{
target = new Avatar("Robot", 100);
}
Avatar a1 = new Avatar("Knight", 80);
profile1.change(a1);
a1 现在指向 Knight 还是 Robot?
Challenge 4
Avatar a2 = profile1.getAvatar();
a2 和 profile1.avatar 是两个不同的 Avatar object,
还是两个 reference 指向同一个 Avatar object?
最后的 Mental Model
avatar1 ────────────┐
│
profile1.avatar ────┼──► Avatar object
│
avatar2 ────────────┘
上图中有 3 个 reference,但只有 1 个 Avatar object。
遇到 reference 题:
new完成后核对
GameProfile.java 完整版本
public class GameProfile
{
private String username;
private int level;
private Avatar avatar;
public GameProfile(String u, int l, Avatar a)
{
username = u;
level = l;
avatar = a;
}
public void printProfile()
{
System.out.println("Username: " + username);
System.out.println("Level: " + level);
System.out.println("Avatar: " + avatar);
}
public void healAvatar(Avatar target, int amount)
{
target.heal(amount);
}
public Avatar getAvatar()
{
return avatar;
}
}
Experiment:Two References, One Object
avatar1.setHealth(20) 后,
profile1.printProfile() 看到的 health 也是 20。
avatar1 ─────────────┐
▼
Avatar
▲
profile1.avatar ─────┘
TODO 3:healAvatar
public void healAvatar(Avatar target, int amount)
{
target.heal(amount);
}
target 和 caller 中的 reference 指向同一个 Avatar object,
所以 target.heal(...) 修改的是共享 object。
tryToReplace
public void tryToReplace(Avatar target)
{
target = new Avatar("Robot", 100);
}
执行重新赋值后:
avatar1 ─────────► Knight
target ─────────► Robot
target 改变了自己保存的 reference。
caller 中的 avatar1 仍然指向原来的 Knight object。
修改 Object vs 修改 Reference
target.heal(30)
→ 修改 target 指向的 object
target = new Avatar(...)
→ 修改 target 自己保存的 reference
TODO 4:getAvatar
public Avatar getAvatar()
{
return avatar;
}
return avatar; 返回的是 reference value。
它不会自动创建一个新的 Avatar object。
profile1.avatar ───┐
▼
Avatar
▲
avatar2 ───────────┘
Challenge Answers
Challenge 1: 10
Challenge 2: 5
Challenge 3: a1 仍然指向 Knight
Challenge 4: 两个 reference 指向同一个 Avatar object