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 指参数)
}
}
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"把变化圈在一处"的延续。
- 必须写在构造方法的第一行——因为"出生流程"还没开始装值,你得先决定叫哪个构造来干
- 一个构造里只能调一次(而且不能两个构造互相调个没完——
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 秒实验:
- 把
this("未录入", 3000);挪到构造方法的第二行(前面加一行打印)——编译报错"必须是构造器中的第一个语句",亲眼确认铁律 1 - 在
Employee()里调this(...),同时在Employee(String, double)里也调this()——报错"递归构造器调用",确认铁律 2 showInfo里的name前面补上this.——照常运行,体会用法一"不撞名可省"
7. 常见误区
this 不是 Car 这张图纸,是堆里某个具体对象。方法执行时 this = 调用者的地址,每次调用可能不同。图纸(类)只有一份,"本人"千千万。
必须第一行。构造方法的任务是完成出生设置,得先"定下按哪个方案出生",才能继续后面的语句。放第二行直接编译报错。
不能,编译报错。static 方法属于类不属于对象(比如 main 方法),没有"本人"可言,this 无从指起。现在混个脸熟,第五章讲 static 时收网。
不对。右边 brand 就近指参数——这正是撞名时的设计意图:左边(属性)和右边(参数)各指各的,才能完成"把参数装进属性"。两边都写 this 反而变成自己赋值给自己。
两个构造互相调用会形成死循环(A 叫 B、B 叫 A,永远出生不了),编译器直接拦截"递归构造器调用"。互调必须是一条单向链:无参 → 半参 → 全参,终点是真正干活的那个。
8. 自测题
this 的值是调用者对象的地址,由"谁调用"决定:car1.setPrice(100) 执行时 this = car1,car2.setPrice(200) 时 this = car2。方法代码只有一份,this 跟着调用者换。
属性永远不被赋值,保持旧值。因为按就近原则,右边 brand 也指参数——整句变成"把参数装进参数"。典型的"不报错但结果不对",所以撞名时 this. 必须写。
①必须写在构造方法第一行——保证先定下"出生方案"再做其他事;②一个构造里只能调一次、且不能互相循环调——防止构造器无限互调、对象永远出生不了。违反都直接编译报错。
调用 Employee(String name);它第一行的 this(name, 3000) 转发给全参构造,真正执行赋值和安检的是 Employee(String, double)。即:半参构造只是转发前台,干活的是全参构造——赋值逻辑全类只写一处。
没有互调时,赋值/安检逻辑要抄进每一个构造方法,改动就得改多处、漏一处就埋 bug;互调后所有构造都转发到全参构造这唯一的赋值现场,规则改一处全类生效——和 3.3 的 setter 安检是同一个思想:入口收敛,规则就不怕散。
9. 下一步学什么
- ☑
3.5 this关键字✅ "我这个"从此有明确的主人:谁调用,this 就是谁 - ☑
3.6 代码块✅ 已完成——三种{ }的出场时机排好了队(静态一次 → 构造块每次 → 构造方法收尾),static 混了个脸熟,详见 3.6 代码块