← Java 基础教程

第 18 / 40 课 · 免费教程

对象军团出发

引用 对象交互与 null

90–110 分钟本章使用 JDK 21,无需预览功能。面向对象入门

本课作品

制作玩家与宠物互动队

会把对象变量理解为引用;能解释两个变量指向同一对象的效果;能在使用对象前处理 null;能通过参数让玩家与宠物安全互动。 前置背包:会声明类、创建对象、访问字段并调用实例方法。今天要看清对象变量这张“坐标纸条”到底指向哪里。

开始前:建议先完成第 17 课

两张标签可能找到同一只狼
  1. #1 · 宠物对象name = 影子
    health = 17
变量引用指向
shadow#1
alias#1

01 / 14

开始前:把示例放对地方

本章使用 JDK 21,不需要预览功能。每个普通示例都给出完整程序,按标注的文件名保存。多个示例各放在自己的文件夹,避免辅助类同名。

终端命令:把中文占位符替换成实际名称

javac -encoding UTF-8 示例文件名.java
java 示例类名

类名区分大小写。完整挑战代码若分成多段,请按顺序接在同一个文件中,不要为续段另建类。运行核对只列程序输出;需要输入的地方另给输入序列。

02 / 14

引用像对象坐标

对象变量不像 int 那样直接装着全部数据。它保存一个引用,让程序能找到对象并访问字段或方法。

对象变量保存一张引用
  1. #1 · 宠物对象name = 影子
    health = 20
变量引用指向
wolf#1

L18Example01.java · 完整独立示例

class PetWolf {
    String name;
    int health = 20;
    int trust;
    void heal(int n) {
        if (n > 0) health += Math.min(n, 20 - health);
    }
    void eat() { trust += Math.min(10, 100 - trust); }
}
public class L18Example01 {
    public static void main(String[] args) {
        PetWolf wolf = new PetWolf();
        wolf.name = "影子";
        System.out.println(wolf.name);
    }
}

运行结果

影子

阅读路线

  • new PetWolf() 创建对象。
  • wolf 保存指向该对象的引用。
  • wolf.name 顺着引用找到字段。
  • wolf.heal(3) 顺着引用调用方法。

03 / 14

两个变量共享一个对象

引用赋值会复制引用。first 和 second 指向同一对象,因此通过 second 修改 health 后,first 读取到的也是新值。

两张引用可以指向同一只宠物
  1. #1 · 宠物对象name = 影子
    health = 1
变量引用指向
first#1
second#1

箭头表示引用关系;不会自动复制对象。

L18Example02.java · 完整独立示例

class PetWolf {
    String name;
    int health = 20;
    int trust;
    void heal(int n) {
        if (n > 0) health += Math.min(n, 20 - health);
    }
    void eat() { trust += Math.min(10, 100 - trust); }
}
public class L18Example02 {
    public static void main(String[] args) {
        PetWolf first = new PetWolf();
        PetWolf second = first;
        second.health = 1;
        System.out.println(first.health);
        System.out.println(first == second);
    }
}

运行结果

1
true

04 / 14

null 表示没有对象

对象变量可以暂时不指向任何对象,此时值是 null。点号左边若为 null,程序无法找到字段或方法。

空引用没有坐标可走
  1. null当前没有对象
  2. 检查先判断是否非null
  3. 存在调用对象方法
  4. 缺失提示或返回失败

L18Example03.java · 完整独立示例

class PetWolf {
    String name;
    int health = 20;
    int trust;
    void heal(int n) {
        if (n > 0) health += Math.min(n, 20 - health);
    }
    void eat() { trust += Math.min(10, 100 - trust); }
}
public class L18Example03 {
    public static void main(String[] args) {
        PetWolf selected = null;
        if (selected != null) System.out.println(selected.name);
        else System.out.println("还没有选择宠物");
    }
}

运行结果

还没有选择宠物

05 / 14

把对象传给方法

对象可以作为参数传入方法。传进去的仍是引用,所以方法修改对象状态,调用处能看见变化。

方法参数也能接收对象引用
  1. 实参传入一个引用值
  2. 形参接收该引用值的副本
  3. 改对象调用者看到状态变化
  4. 改参数不改变调用者的引用

L18Example04.java · 完整独立示例

class PetWolf {
    String name;
    int health = 20;
    int trust;
    void heal(int n) {
        if (n > 0) health += Math.min(n, 20 - health);
    }
    void eat() { trust += Math.min(10, 100 - trust); }
}
public class L18Example04 {
    static void healPet(PetWolf pet, int n) {
        if (pet != null) pet.heal(n);
    }
    static void replacePet(PetWolf pet) { pet = new PetWolf(); }
    public static void main(String[] args) {
        PetWolf wolf = new PetWolf(); wolf.health = 12;
        healPet(wolf, 5);
        replacePet(wolf);
        healPet(null, 5);
        System.out.println(wolf.health);
    }
}

运行结果

17

06 / 14

对象通过方法互动

对象互动不是让一个类随便改另一个类的字段。更清楚的方式是调用对方的方法。

玩家发出动作 宠物维护自己的状态
  1. Player负责选择互动对象
  2. feed调用宠物eat
  3. PetWolf维护信任范围
  4. 缺失明确处理null

L18Example05.java · 完整独立示例

class PetWolf {
    String name;
    int health = 20;
    int trust;
    void heal(int n) {
        if (n > 0) health += Math.min(n, 20 - health);
    }
    void eat() { trust += Math.min(10, 100 - trust); }
}
class Player {
    void feed(PetWolf pet) { if (pet != null) pet.eat(); }
}
public class L18Example05 {
    public static void main(String[] args) {
        Player alex = new Player();
        PetWolf pet = new PetWolf();
        alex.feed(pet);
        alex.feed(null);
        System.out.println(pet.trust);
    }
}

运行结果

10

07 / 14

改引用和改对象不同

second.health = 5 修改对象;second = new PetWolf() 改变 second 保存的引用。first 仍指向旧对象。

重新赋值只改变那一张引用
  1. #1 · 对象 Ahealth = 20
  2. #2 · 对象 Bhealth = 5
变量引用指向
first#1
second#2

L18Example06.java · 完整独立示例

class PetWolf {
    String name;
    int health = 20;
    int trust;
    void heal(int n) {
        if (n > 0) health += Math.min(n, 20 - health);
    }
    void eat() { trust += Math.min(10, 100 - trust); }
}
public class L18Example06 {
    public static void main(String[] args) {
        PetWolf first = new PetWolf();
        PetWolf second = first;
        second = new PetWolf();
        second.health = 5;
        System.out.println(first.health + "," + second.health);
        System.out.println(first == second);
    }
}

运行结果

20,5
false

08 / 14

引用相等和内容相等

对对象使用 ==,比较的是两个引用是否指向同一对象。两只同名宠物仍可能是不同对象。

双等号比较是否指向同一对象
  1. ==比较引用身份
  2. sameName本例比较名字内容
  3. 同名不代表同一对象
  4. equals是否重写会影响语义

L18Example07.java · 完整独立示例

class PetWolf {
    String name;
    int health = 20;
    int trust;
    void heal(int n) {
        if (n > 0) health += Math.min(n, 20 - health);
    }
    void eat() { trust += Math.min(10, 100 - trust); }
    boolean sameName(PetWolf other) {
        return other != null && java.util.Objects.equals(name, other.name);
    }
}
public class L18Example07 {
    public static void main(String[] args) {
        PetWolf a = new PetWolf(); a.name = "影子";
        PetWolf b = a;
        PetWolf c = new PetWolf(); c.name = "影子";
        System.out.println(a == b);
        System.out.println(a == c);
        System.out.println(a.sameName(c));
    }
}

运行结果

true
false
true

09 / 14

对象军团测试台

测试对象程序时,记录变量指向哪个对象,以及每个对象当前字段值。

测试引用图比只看输出更可靠
  1. 两次new验证独立状态
  2. 共享赋值验证修改可见
  3. 重新指向验证旧引用未变化
  4. null验证安全拒绝
验收表
场景操作预期
独立两次new两个对象互不影响
共享b = ab修改后a也看到
改引用b = new PetWolf()a仍指向旧对象
空引用b = null检查后不调用方法

10 / 14

null 故障诊所

NullPointerException 通常给出具体行号。找到该行所有点号,检查每个对象变量的来源。

沿着异常栈找到第一个危险点号
  1. 看行号定位自己的代码
  2. 找引用检查点号左边
  3. 查来源追踪赋值与返回
  4. 修复为缺失对象安排分支

L18Example09.java · 完整独立示例

class PetWolf {
    String name;
    int health = 20;
    int trust;
    void heal(int n) {
        if (n > 0) health += Math.min(n, 20 - health);
    }
    void eat() { trust += Math.min(10, 100 - trust); }
}
public class L18Example09 {
    static PetWolf findPet(String name) { return null; }
    public static void main(String[] args) {
        PetWolf found = findPet("影子");
        if (found != null) found.heal(5);
        else System.out.println("没有这只宠物");
    }
}

运行结果

没有这只宠物

急诊顺序

  • 读异常类型和第一条属于自己代码的行。
  • 圈出点号左边的对象变量。
  • 追踪它从哪里赋值或返回。
  • 选择检查 null、提供默认对象或拒绝输入。

11 / 14

挑战 蓝图 玩家与宠物

挑战 程序叫 PlayerPetTeam。玩家持有当前宠物引用;选择成功后才能喂食或治疗。

三个对象方法完成喂食 治疗和选择
  1. Player.selected保存当前选择
  2. PetWolf维护生命与信任
  3. 互动调用宠物方法
  4. 取消null清除选择

验收条件

  • 两只宠物是独立对象。
  • 玩家选择第一只宠物后喂食,信任值增加。
  • 把另一个变量指向同一宠物并治疗,原引用看到新生命。
  • 取消选择设为null后,互动返回false而不是崩溃。
验收表
方法输入输出或效果
selectPetWolf保存当前引用
feedSelected无信任增加或false
healSelected数量生命增加或false
status无打印宠物状态

12 / 14

挑战 实现 玩家宠物队

新建 PlayerPetTeam.java。运行前预测每行输出,再画出 alex.selected、alias、shadow 与 snow 指向的对象。

引用关系在完整程序里变成可验证行为
  1. 选择影子selected指向影子
  2. 喂食信任增加10
  3. alias治疗原对象生命变17
  4. 取消互动返回false

PlayerPetTeam.java · 第1/2段,顺序接在同一文件

public class PlayerPetTeam {
    static class PetWolf {
        String name;
        int health = 20;
        int trust = 0;
        void eat() { trust += Math.min(10, 100 - trust); }
        void heal(int n) {
            if (n > 0) health += Math.min(n, 20 - health);
        }
        void status() {
            System.out.println(name + " 生命=" + health + " 信任=" + trust);
        }
    }
    static class Player {
        String name;
        PetWolf selected;
        void select(PetWolf pet) { selected = pet; }
        boolean feedSelected() {
            if (selected == null) return false;
            selected.eat(); return true;
        }
        boolean healSelected(int n) {
            if (selected == null || n <= 0) return false;
            selected.heal(n); return true;
        }
    }
    public static void main(String[] args) {
        PetWolf shadow = new PetWolf(); shadow.name = "影子";

PlayerPetTeam.java · 第2/2段,顺序接在同一文件

        PetWolf snow = new PetWolf(); snow.name = "雪球";
        shadow.health = 12;
        Player alex = new Player(); alex.name = "Alex";
        alex.select(shadow);
        System.out.println(alex.feedSelected());
        PetWolf alias = shadow;
        alias.heal(5);
        shadow.status();
        snow.status();
        alex.select(null);
        System.out.println(alex.healSelected(3));
    }
}

运行结果

true
影子 生命=17 信任=10
雪球 生命=20 信任=0
false

13 / 14

拓展任务与通关复盘

作业除代码外,再交一张引用图:变量画成标签,对象画成方块,用箭头连接。

三档任务训练对象箭头
  1. 木镐证明共享修改可见
  2. 铁镐证明参数按值传递
  3. 钻石镐三只宠物选择系统
  4. 复盘画变量与对象箭头

三档作业

  • 木镐:创建一个 Mob,让两个变量共享它并证明修改可见。
  • 铁镐:写方法 heal(Mob target, int amount),安全处理 null,并证明给参数重新赋值不改变调用者的引用。
  • 钻石镐:制作三只宠物的选择系统,支持选择、取消、喂食和状态查询。
验收表
自评能还要练
我能画引用箭头□□
我能解释共享对象□□
我会安全处理null□□

14 / 14

自己的验收记录

运行之前写预期,运行之后记录实际结果。正常案例说明程序会做事;边界案例说明它知道何时停;错误案例说明它能拒绝不合理的请求。

把证据写进记录
类别你选择的输入预期与实际
正常一个常用值或一次成功操作是否逐行一致?
边界最小值、最大值及相邻值有没有少一格、多一步?
错误非法数量、缺失对象或错误语法是否明确拒绝?状态是否保持?
  • 把一个 Bug 写成:触发条件、现象、原因、最小修复。
  • 整理一个值得记住的规则,用自己的话解释,不能只复制定义。
  • 改变一个示例,保留测试记录,让结果证明你的理解。

TAKE IT WITH YOU

下载本课 PDF

完整课文、代码、图解与练习,选择你需要的语言。