构造方法
本教程共 100 篇 · 第 33 篇 · 更新于 2026-08-05 · 约 6 分钟阅读
33. 构造方法
本节目标:搞懂构造方法为什么存在、和普通方法差在哪,会写无参和带参构造,能解释默认构造的消失规则,并用 this() 把多个构造串成一条链。
创建对象时,通常希望”一造出来就带着合理的数据”。构造方法就是干这个的:它在 new 的那一瞬间自动执行。
没有构造方法会怎样
先看不用构造方法的写法:
Student s = new Student();
s.name = "小明";
s.age = 18;
s.school = "一中";
```bash
四行才把一个对象装配好。问题有三个:
- **容易漏**。少写一行 age,对象就带着 0 岁上路了,编译器不管。
- **中间态危险**。第一行执行完到第四行之前,这个对象是残缺的。如果中间被别的代码拿去用,行为不可预测。
- **没法强制**。想要求"学生必须有名字",靠注释提醒是拦不住人的。
构造方法把装配过程压缩成一步,而且是**强制的一步**。
## 构造方法长什么样
它和类同名,**没有返回类型**,连 void 都不写。
```java
public class Student {
String name;
int age;
Student() { // 无参构造,方法名 = 类名
name = "未知";
age = 0;
}
}
new Student() 时,这段代码自动跑一遍,name 和 age 就有了初值。
和普通方法比,它有四点不同:
| 对比项 | 构造方法 | 普通方法 |
|---|---|---|
| 方法名 | 必须和类名完全一致 | 自己取,小驼峰 |
| 返回类型 | 不写,连 void 都不能有 | 必须写,无返回值写 void |
| 调用方式 | 只能被 new 触发(或 this()/super()) | 用”对象.方法名()“主动调 |
| 调用次数 | 每个对象一生只跑一次 | 想调多少次调多少次 |
加了 void 会怎样
这是个经典陷阱:
public class Student {
String name;
void Student() { // 加了 void,它不再是构造方法
name = "未知";
}
}
```bash
编译**完全通过**,没有任何警告。但它已经退化成一个"名字碰巧和类同名"的普通方法。`new Student()` 调的是编译器送的默认构造,你写的这段根本不会执行,name 依然是 null。
> [!WARNING]
> 构造方法写了 void 是初学者最难自查的 bug 之一,因为编译器不报错,只是行为不对。发现"构造里的赋值没生效",第一件事就是看有没有多写了返回类型。
## 默认构造方法
一个构造都不写时,编译器会偷偷送你一个无参、空实现的构造:
```java
public class Dog {
String name;
}
// 等价于编译器帮你补了这一段
// Dog() { super(); }
所以 new Dog() 能跑通。这个赠品叫默认构造方法。
规则的关键在于:只要你自己写了任何一个构造,赠品立刻收回。
public class Student {
String name;
int age;
Student(String name, int age) { // 只写了带参构造
this.name = name;
this.age = age;
}
}
```java
这时再写 `new Student()` 就炸了:
```text
error: constructor Student in class Student cannot be applied to given types;
Student s = new Student();
^
required: String,int
found: no arguments
reason: actual and formal argument lists differ in length
报错读法:required 是”我要的参数”,found 是”你给的参数”,reason 说明对不上的原因是个数不同。需要无参版本,自己补一个就是了。
带参构造:一步到位
public class Student {
String name;
int age;
Student(String name, int age) {
this.name = name; // this.name 是字段,右边 name 是参数
this.age = age;
}
}
// 使用
Student s = new Student("小明", 18); // 造出来就是完整的
```java
参数名和字段名保持一致是主流风格,代价是必须写 `this.`,否则等于把参数赋给自己,字段纹丝不动。this 的细节下一章讲。
带参构造还能顺手做校验,把非法数据挡在对象诞生之前:
```java
Student(String name, int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("年龄不合法:" + age);
}
this.name = name;
this.age = age;
}
对象要么造出来就是合法的,要么根本造不出来。这比”造完再检查”可靠得多。
构造方法重载
构造方法也能重载,规则和普通方法一样:参数列表不同即可。
public class Student {
String name;
int age;
Student() {
this.name = "未知";
this.age = 0;
}
Student(String name) {
this.name = name;
this.age = 0;
}
Student(String name, int age) {
this.name = name;
this.age = age;
}
}
```java
调用方按需挑一个:
```java
Student a = new Student();
Student b = new Student("小明");
Student c = new Student("小红", 17);
问题也来了:三个构造里 this.age = 0 和 this.name = name 反复出现。哪天加一条校验规则,三处都得改,漏一处就是 bug。
用 this() 串成构造链
this(参数) 表示”调用本类的另一个构造方法”。把公共逻辑集中到参数最全的那个里:
public class Student {
String name;
int age;
Student() {
this("未知", 0); // 转交给双参构造
}
Student(String name) {
this(name, 0); // 也转交给双参构造
}
Student(String name, int age) { // 唯一真正干活的构造
if (age < 0 || age > 150) {
throw new IllegalArgumentException("年龄不合法:" + age);
}
this.name = name;
this.age = age;
}
}
```text
现在校验逻辑只有一份,三个入口全都受它保护。这种"参数少的转交给参数多的"写法叫**构造链**,是标准套路。
用 this() 有两条硬规矩。
第一,它**必须是构造方法体的第一条语句**。写在后面直接报错:
```text
error: call to this must be first statement in constructor
第二,不能兜圈子。A 调 B、B 又调 A,编译器会识破:
error: recursive constructor invocation
```bash
> [!NOTE]
> Java 25 把这条"必须第一条"的规则放宽了(JEP 513 灵活构造体,已转正)。现在允许在 this() 或 super() 之前写一小段前导语句,用来校验参数、算中间值。前提是这些语句**不能引用正在构造的对象**:不能读字段、不能调实例方法、不能把 this 传出去。对初学者来说,仍然建议直接把 this() 写在第一行,最省心也最通用。
## 构造方法总是从父类开始跑
前面那段注释里的 `super()` 不是随手写的。Java 规定:任何构造方法执行前,父类的构造必须先跑完。你不写,编译器就自动插一句无参的 super()。
```java
class Animal {
Animal() {
System.out.println("Animal 构造执行");
}
}
class Cat extends Animal {
Cat() {
// 这里被编译器悄悄插入了 super();
System.out.println("Cat 构造执行");
}
}
new Cat() 的输出顺序是先 Animal 后 Cat。道理和盖楼一样:地基没打好,二层无从谈起。父类字段先初始化完毕,子类构造才能放心使用它们。
这也解释了为什么 this() 和 super() 不能同时出现在一个构造里——它俩抢的是同一个位置,而且各自都会引发一次父类初始化。用了 this() 转交,父类构造由链条末端那个构造去触发。继承的完整规则在第 39 和 41 章展开。
this() 和 this.属性 不是一回事
两者都带 this,用途完全不同:
this(参数)—— 调用本类的另一个构造方法,只能出现在构造方法里,且最多一次。this.属性—— 访问当前对象的字段,任何实例方法里都能用。
记混了会写出 this.Student(...) 这种不存在的语法。下一章会把 this 的所有用法归拢讲一遍。
常见疑问
Q:构造方法能不能加 private?
A:能。私有构造意味着外部无法 new,只能通过类内部提供的静态方法拿对象。工具类常用这招把无参构造设成 private,防止别人误建实例。
Q:构造方法里能调用普通方法吗?
A:语法上可以,但要当心。构造还没跑完,对象处于半成品状态,此时调用的方法如果读了还没赋值的字段,拿到的是默认值。尤其别在构造里调用可能被子类重写的方法。
Q:一个类可以完全不写构造方法吗?
A:可以,编译器会补默认构造。但只要你需要”创建时必须传入某些数据”,就该显式写带参构造,让编译器帮你强制这个约定。
Tip判断标准很简单:这个字段”没有它对象就不完整”,就放进构造方法参数里;可有可无的,留给 setter 后补。能用带参构造一步到位的,别 new 完再零散赋值。