☕ 第三章 面向对象(上)

3.5 this关键字

📄 一句话概括

this 就是当前对象本人——谁调用了这个方法,this 就是谁。它有三种用法:this.属性(区分撞名)、this.方法()(喊本类方法)、this(...)(构造方法互调)。


1. this 是谁——"当前"由谁决定

3.3 学过"this 读作'我这个'",但留了个尾巴:方法里的 this,凭什么知道自己是哪辆车? 堆里明明停着 car1、car2 两辆,setPrice 只有一份代码,它怎么不装错车?

答案在上面那张图里:this 是方法自带的一个隐形参数,值 = 调用者的地址。

car1.setPrice(100);   // 执行 setPrice 时,this = car1(0xAA01)
car2.setPrice(200);   // 执行 setPrice 时,this = car2(0xBB02)

规则只有一条:谁调用,this 就是谁。同一份方法代码,每次执行时 this 换成不同的调用者——这就是"当前对象"的含义。

💡 用"给我自己"读一遍

this.price = price; → "把传来的值,装进调用者本人的 price 格子"。 car1.setPrice(100) 时,"本人"是 car1;car2.setPrice(200) 时,"本人"是 car2。方法只有一份,"本人"跟着调用者走。

和 3.2 的栈/堆呼应:方法代码只有一份(跟着类走),但每个对象各有自己的属性格子(在堆里)。this 就是方法执行时拿到的"本人地址",靠它操作到正确的格子——3.2 说的"钥匙开对应的车",this 就是那把正在用的钥匙。


2. 用法一:this.属性——区分撞名(复习 + 收网)

3.3 讲过的内容,这里正式收网:

public class Car {
    private String brand;   // 属性

    public void setBrand(String brand) {   // 参数撞名了
        this.brand = brand;
        //     ↑属性     ↑参数(就近原则,直接写 brand 指参数)
    }
}
⚠️ 亲手验证:去掉 this 会怎样

brand = brand; 编译不报错,运行也不报错——但属性永远是旧值!因为右边 brand 按就近原则也指参数,等于"把参数装进参数",属性的格子根本没被碰过。这是"不报错但结果不对"逻辑 bug 的典型,比报错难查十倍。3.3 误区 3 说的"初学全写上 this",理由就在这。

不撞名时 this. 可以省——但省不省不影响语义:

public void setPrice(double p) {
    this.price = p;   // 写全,等价于 price = p;
}

3. 用法二:this.方法()——喊本类的方法

方法里可以喊本类的其他方法:

public void run() {
    this.check();   // this. 可以省,写成 check() 一个意思
    System.out.println(brand + " 在跑");
}

同一个对象内部互喊方法,this. 通常省略——IDEA 里写了它还会提示你"冗余"。知道有这用法即可,重点在用法三。


4. 用法三:this(...)——构造方法互调(本节重点)

3.4 的 Car 类里,无参构造和带参构造各自干各的:

public Car() {
    this.brand = "未知品牌";    // 赋值逻辑写了一遍
}

public Car(String brand, double price) {
    this.brand = brand;          // 又写了一遍!
    this.setPrice(price);
}

问题:赋值/安检逻辑散在两个构造里,将来改一处忘一处就出 bug。解法——让一个构造方法调用另一个:

public class Car {
    private String brand;
    private double price;

    public Car() {
        this("未知品牌", 0);      // ← 把出生设置转发给带参构造,一行搞定
    }

    public Car(String brand, double price) {
        this.brand = brand;        // 真正的赋值逻辑只在这里写一遍
        this.setPrice(price);      // 安检也只走这一处
    }
}

this("未知品牌", 0); 读作:"按带参的方式出生一次,参数是这两个"——无参构造不再自己干活,只当个"转发前台"。所有赋值和安检都收拢在带参构造一处,这正是 3.3"把变化圈在一处"的延续。

⚠️ this(...) 两条铁律(编译器强制)
  1. 必须写在构造方法的第一行——因为"出生流程"还没开始装值,你得先决定叫哪个构造来干
  2. 一个构造里只能调一次(而且不能两个构造互相调个没完——Car() 调 Car(String),Car(String) 又调 Car(),编译器直接拒绝:递归构造器调用)

违反任何一条,编译报错——好消息是这类错误当场暴露,不用运行才发现。


5. 什么时候用哪个——一张表

写法 意思 典型场景 能省吗
this.属性 本人(调用者)的属性 参数和属性撞名时必须写;不撞名可省 撞名不能省
this.方法() 喊本人的方法 类内部互喊 几乎都省
this(...) 按另一种方式出生 无参构造复用带参构造 不能省,且必须第一行

6. 动手部分:用 this(...) 收拢构造逻辑

第 1 步:在 IDEA02 里新建类 Employee(员工,换个人物练):

public class Employee {
    private String name;
    private double salary;

    // 无参构造:只当转发前台,自己不干活
    public Employee() {
        this("未录入", 3000);       // 默认底薪 3000
    }

    // 只给名字:工资走默认
    public Employee(String name) {
        this(name, 3000);           // ← 转发给全参构造
    }

    // 全参构造:唯一的赋值现场
    public Employee(String name, double salary) {
        this.name = name;
        if (salary >= 0) {
            this.salary = salary;
        } else {
            System.out.println("工资不能为负,按 0 入职");
            this.salary = 0;
        }
    }

    public String getName() { return name; }
    public double getSalary() { return salary; }

    public void showInfo() {
        System.out.println("员工:" + name + ",工资:" + salary);
    }
}

第 2 步:新建测试类 TestEmployee:

public class TestEmployee {
    public static void main(String[] args) {
        Employee e1 = new Employee();
        Employee e2 = new Employee("波仔");
        Employee e3 = new Employee("老板", -50);

        e1.showInfo();   // 员工:未录入,工资:3000.0
        e2.showInfo();   // 员工:波仔,工资:3000.0
        e3.showInfo();   // 工资不能为负,按 0 入职 → 员工:老板,工资:0.0
    }
}

看到什么算成功:三行输出,其中 e3 先打印安检提示。注意 赋值和安检的代码全类只出现了一处(全参构造里)——这就是 this(...) 的价值。

三个 30 秒实验:

  1. 把 this("未录入", 3000); 挪到构造方法的第二行(前面加一行打印)——编译报错"必须是构造器中的第一个语句",亲眼确认铁律 1
  2. 在 Employee() 里调 this(...),同时在 Employee(String, double) 里也调 this()——报错"递归构造器调用",确认铁律 2
  3. showInfo 里的 name 前面补上 this.——照常运行,体会用法一"不撞名可省"

7. 常见误区

⚠️ 误区 1:this 指的是类

this 不是 Car 这张图纸,是堆里某个具体对象。方法执行时 this = 调用者的地址,每次调用可能不同。图纸(类)只有一份,"本人"千千万。

⚠️ 误区 2:this(...) 可以写在构造方法的任意位置

必须第一行。构造方法的任务是完成出生设置,得先"定下按哪个方案出生",才能继续后面的语句。放第二行直接编译报错。

⚠️ 误区 3:static 方法里能用 this

不能,编译报错。static 方法属于类不属于对象(比如 main 方法),没有"本人"可言,this 无从指起。现在混个脸熟,第五章讲 static 时收网。

⚠️ 误区 4:this.brand = brand 里右边也该加 this

不对。右边 brand 就近指参数——这正是撞名时的设计意图:左边(属性)和右边(参数)各指各的,才能完成"把参数装进属性"。两边都写 this 反而变成自己赋值给自己。

⚠️ 误区 5:构造方法互调随便调,不会出问题

两个构造互相调用会形成死循环(A 叫 B、B 叫 A,永远出生不了),编译器直接拦截"递归构造器调用"。互调必须是一条单向链:无参 → 半参 → 全参,终点是真正干活的那个。


8. 自测题

❓ Q1:方法执行时,this 的值是什么?由什么决定?

this 的值是调用者对象的地址,由"谁调用"决定:car1.setPrice(100) 执行时 this = car1,car2.setPrice(200) 时 this = car2。方法代码只有一份,this 跟着调用者换。

❓ Q2:`brand = brand;`(参数与属性同名、没写 this)编译和运行都正常,但结果是什么?为什么?

属性永远不被赋值,保持旧值。因为按就近原则,右边 brand 也指参数——整句变成"把参数装进参数"。典型的"不报错但结果不对",所以撞名时 this. 必须写。

❓ Q3:this(...) 的两条铁律是什么?各防止什么问题?

①必须写在构造方法第一行——保证先定下"出生方案"再做其他事;②一个构造里只能调一次、且不能互相循环调——防止构造器无限互调、对象永远出生不了。违反都直接编译报错。

❓ Q4:`new Employee("波仔")` 调用了哪个构造?执行顺序是怎样的?

调用 Employee(String name);它第一行的 this(name, 3000) 转发给全参构造,真正执行赋值和安检的是 Employee(String, double)。即:半参构造只是转发前台,干活的是全参构造——赋值逻辑全类只写一处。

❓ Q5(挑战):为什么说 this(...) 是"把变化圈在一处"的又一案例?用自己的话说清。

没有互调时,赋值/安检逻辑要抄进每一个构造方法,改动就得改多处、漏一处就埋 bug;互调后所有构造都转发到全参构造这唯一的赋值现场,规则改一处全类生效——和 3.3 的 setter 安检是同一个思想:入口收敛,规则就不怕散。


9. 下一步学什么


相关笔记