Java 构造器有什么特点?this() 和 super() 有什么区别?默认构造器什么时候生成?
简化版
构造器(构造方法)是「创建对象时初始化它」的特殊方法——名字和类名相同、没有返回类型、new 时自动调用。几个关键点:① 默认构造器——如果你一个构造器都不写,编译器自动生成一个无参构造器;但只要你写了任意一个构造器,编译器就不再生成默认的(此时如果还想要无参构造器得自己写)。② this() 调用本类的其他构造器(构造器重载间复用),super() 调用父类的构造器;两者都必须放在构造器的第一行,所以 this() 和 super() 不能同时出现。③ 构造器链——创建子类对象时,会先调父类构造器(super()),一路向上到 Object,保证「先构造好父类地基,再构造子类」。如果子类构造器没显式写 super(),编译器自动插入无参 super()。
详细版
构造器的特点:
public class User {
private String name;
// 构造器:名字=类名、无返回类型、new 时调用
public User(String name) { this.name = name; }
}
User u = new User("Tom"); // new 时调用构造器
默认构造器的生成规则:
// 情况1:一个构造器都不写 → 编译器自动生成无参构造器
class A { } // 等价于 class A { A() {} }
new A(); // ✓ 能用无参
// 情况2:写了带参构造器 → 编译器不再生成默认无参构造器
class B { B(int x) {} }
new B(); // ✗ 编译错误!没有无参构造器了
new B(1); // ✓ 只能用你写的带参
// 想要无参得自己补:B() {}
this() 和 super():
| this() | super() | |
|---|---|---|
| 调用 | 本类的其他构造器 | 父类的构造器 |
| 位置 | 构造器第一行 | 构造器第一行 |
| 用途 | 构造器重载间复用代码 | 初始化父类部分 |
| 同时用 | ✗ 不能(都要在第一行) | ✗ 不能 |
class User {
private String name;
private int age;
public User(String name) {
this(name, 18); // this() 调用本类另一个构造器(复用),必须第一行
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
构造器链(子类创建时):
class Parent { Parent() { System.out.println("Parent 构造"); } }
class Child extends Parent {
Child() {
// 编译器自动在第一行插入 super();
System.out.println("Child 构造");
}
}
new Child(); // 输出:Parent 构造 → Child 构造(先父后子)
⚠️ 如果父类没有无参构造器(只有带参的),子类构造器必须显式调用
super(参数)——否则编译器想自动插入的无参super()找不到,编译报错。这是新手常踩的坑:父类写了带参构造器(导致无参默认构造器不再生成),子类却没显式super(参数),就编译不过。解决:要么父类补一个无参构造器,要么子类每个构造器显式super(参数)。
完整版教学
一、构造器的本质:初始化对象
构造器是「创建对象时用来初始化它的特殊方法」,有几个和普通方法不同的特点:
构造器的特点:
① 方法名必须和类名完全相同(包括大小写)
② 没有返回类型(连 void 都不写)——它不是"返回一个对象",是"初始化对象"
③ new 对象时自动调用(不能手动像普通方法那样调)
④ 可以重载(多个构造器,参数不同)
⑤ 不能被继承、不能被重写(每个类有自己的构造器)
构造器的职责是「在对象创建出来后、被使用前,把它初始化到一个合理的状态」——给字段赋初值、建立必要的依赖、做校验。注意区分「实例化」和「初始化」:new 先分配内存(实例化),然后构造器负责初始化(赋值)。构造器没有返回类型是因为它「不返回值」——它的作用是初始化那个正在创建的对象(new 表达式返回对象引用,不是构造器返回)。理解「构造器是初始化对象的特殊方法、名同类名无返回类型、new 时自动调」,就抓住了它的本质——它是对象生命周期的起点。
二、默认构造器:写不写的区别
「默认构造器」是最容易踩坑的点——它只在「你一个构造器都不写」时才自动生成:
规则:
你没写任何构造器 → 编译器自动生成一个 public 无参构造器(默认构造器)
你写了至少一个构造器(哪怕是带参的)→ 编译器不再生成默认构造器
class A {} → 编译器补 A() {} → new A() 可用
class B { B(int x){} } → 编译器不补无参 → new B() 报错(只有 B(int))
这个规则的坑在于「写了带参构造器后,无参构造器就『消失』了」:
class User {
private String name;
public User(String name) { this.name = name; } // 写了带参构造器
}
new User(); // ✗ 编译错误!默认无参构造器不再生成
new User("Tom"); // ✓ 只能用带参
很多框架(如 JavaBean 规范、Jackson 反序列化、JPA 实体)要求类有无参构造器(它们要先 new 一个空对象再反射赋值),如果你只写了带参构造器就会出问题。解决:需要无参构造器时显式补一个 public User() {}。理解「默认构造器只在没写任何构造器时生成、写了带参就要自己补无参」,就避开了这个高频坑——尤其是用反射/序列化框架时。
三、this():构造器间复用
this()(带括号,注意和 this.字段 区分)用于「在一个构造器里调用本类的另一个构造器」,实现构造器重载间的代码复用:
class Rectangle {
private int width, height;
// 正方形:复用带两参的构造器
public Rectangle(int side) {
this(side, side); // this() 调用本类的 (int, int) 构造器,必须第一行
}
public Rectangle(int width, int height) { // 真正干活的构造器
this.width = width;
this.height = height;
}
}
this() 的价值是「避免多个构造器里重复写初始化逻辑」——让简单的构造器「委托」给完整的构造器。规则:① 必须是构造器的第一条语句(因为要先完成初始化的委托);② 只能在构造器里用(普通方法里不能调构造器);③ 不能形成循环调用(A 调 B、B 调 A 会编译错误)。这是「构造器链」的一种(本类内部的链)。理解「this() 调本类其他构造器、复用初始化逻辑、必须第一行」,就掌握了构造器重载的复用手段——把公共初始化收敛到一个「主构造器」,其他构造器委托给它。
四、super():调用父类构造器
super() 用于「调用父类的构造器」,初始化对象的「父类部分」:
class Animal {
private String name;
public Animal(String name) { this.name = name; }
}
class Dog extends Animal {
private String breed;
public Dog(String name, String breed) {
super(name); // super() 调用父类 Animal 的构造器,必须第一行
this.breed = breed; // 再初始化子类自己的字段
}
}
super() 的作用是「先把从父类继承来的部分初始化好,再初始化子类自己的部分」——因为子类对象「包含」父类的部分(继承的字段),必须先构造好父类地基。规则:① 必须是构造器的第一条语句;② 如果不写,编译器自动插入无参 super()(调父类无参构造器)。这引出前面的坑——如果父类没有无参构造器(只有带参的),编译器想自动插的无参 super() 找不到,就必须显式 super(参数)。理解「super() 调父类构造器、初始化父类部分、不写则自动插无参 super()」,就理解了继承体系下对象是怎么「从父到子」逐层构造的。
五、this() 和 super() 为什么不能同时用
一个经典问题:this() 和 super() 不能同时出现在一个构造器里,原因很简单——它们都必须是「第一条语句」,而一个构造器只有一个「第一条语句」:
class Child extends Parent {
Child() {
super(); // 想调父类构造器
this(0); // ✗ 编译错误!this() 和 super() 都要在第一行,冲突
}
}
为什么都要在第一行?因为初始化必须有明确的顺序——要么先委托给本类另一个构造器(this())、要么先初始化父类(super()),不能既委托又初始化父类(会导致父类被初始化两次或顺序混乱)。逻辑上:如果用了 this() 委托给本类另一个构造器,那个构造器里会(直接或间接)调 super()——所以最终父类一定会被初始化,只是「这个构造器」把这件事委托出去了。所以「this() 或 super() 二选一在第一行」——用 this() 就把 super 的责任交给被委托的构造器。理解「this() 和 super() 都要第一行所以不能同时用、用 this() 时 super 由被委托的构造器负责」,就理解了这个约束的必然性。
六、构造器执行的完整顺序
把 this()、super()、字段初始化、构造器体串起来,就是对象创建的完整初始化顺序(前面「类初始化顺序」题讲过整体,这里聚焦构造器视角):
new Child() 时(Child extends Parent):
1. 进入 Child 构造器
2. 第一行是 super()(显式或编译器自动插入)→ 先执行 Parent 构造器
(Parent 构造器里同样先 super() 到 Object,一路向上)
3. Parent 部分构造完,回到 Child
4. 执行 Child 的实例字段初始化和实例代码块(编译器插在 super() 之后、构造器体之前)
5. 执行 Child 构造器体的剩余代码
如果 Child 构造器第一行是 this(...)(而非 super):
→ 先委托给本类另一个构造器(那个构造器负责 super() 和字段初始化)
→ 委托的构造器执行完,再执行本构造器体剩余代码
关键顺序:super() → 父类完整构造 → 本类字段初始化和实例块 → 本类构造器体。「父类先于子类构造」保证了「用子类构造器时父类部分已就绪」(否则子类构造器里用继承的字段会是未初始化的)。而字段初始化和实例块被编译器插在 super() 之后、构造器体之前——所以字段的显式初始值在构造器体执行前就绪。理解「构造器执行顺序:super 先行→父类构造→本类字段/实例块→本类构造器体」,就把构造器和对象初始化完整串起来了。
记忆钩子:「构造器=初始化对象(名同类名、无返回类型、new 时调);默认构造器只在『没写任何构造器』时生成,写了带参就要自己补无参(反射/序列化框架需要无参);this() 调本类其他构造器(复用)、super() 调父类构造器(初始化父类部分),都必须第一行、不能同时用;构造器链先父后子(不写自动插无参 super(),父类无无参构造器时子类必须显式 super(参数))」。
七、常见误区与追问
- 误区:每个类都有默认无参构造器。 只在「你一个构造器都没写」时编译器才自动生成;只要写了任意一个构造器(哪怕带参),默认无参构造器就不再生成。
- 误区:this() 和 super() 可以同时写在一个构造器里。 不能——两者都必须是构造器第一条语句,冲突;用 this() 时 super() 由被委托的那个构造器负责。
- 误区:构造器有返回类型 void。 没有任何返回类型(连 void 都不写)——它不返回值,作用是初始化对象;写了返回类型就变成普通方法了(一个易错点)。
- 误区:父类有带参构造器,子类不写 super 也能编译。 不一定——父类只有带参构造器时(无参默认构造器不生成),编译器自动插的无参 super() 找不到,子类必须显式 super(参数),否则编译错误。
- 追问:this() 和 super() 有什么区别? this() 调用本类的其他构造器(构造器重载间复用代码),super() 调用父类的构造器(初始化从父类继承的部分);都必须在构造器第一行、不能同时用。
- 追问:什么时候编译器不生成默认构造器? 只要你写了至少一个构造器(无论有参无参),编译器就不再生成默认无参构造器;此时若需要无参构造器(如反射/序列化框架要求)得自己写。
- 追问:为什么创建子类对象会先执行父类构造器? 子类对象包含从父类继承的部分,必须先构造好父类地基再构造子类;子类构造器第一行的 super()(显式或自动插入)保证了这个顺序,一路向上到 Object。
八、加强记忆
构造器是「创建对象时初始化它」的特殊方法(名同类名、无返回类型、new 时自动调、可重载、不能被继承重写)。默认构造器的规则是坑点:只在「你一个构造器都不写」时编译器才自动生成无参构造器;一旦写了任意构造器(哪怕带参),默认无参就不再生成——所以只写带参构造器后 new B() 会报错,而反射/序列化框架(Jackson/JPA/JavaBean)常要求无参构造器,需要时得自己补。this() 调用本类其他构造器(构造器重载间复用,把公共初始化收敛到主构造器),super() 调用父类构造器(初始化从父类继承的部分);两者都必须是构造器第一条语句、不能同时用(用 this() 时 super 由被委托的构造器负责)。构造器链先父后子——子类构造器第一行的 super()(不写则编译器自动插无参 super())保证「先构造父类地基再构造子类」,一路向上到 Object;若父类只有带参构造器(无参默认不生成),子类必须显式 super(参数),否则编译错误。执行顺序:super()→父类构造→本类字段/实例块→本类构造器体。一句话「构造器初始化对象、默认构造器只在没写任何构造器时生成、this() 调本类 super() 调父类都在第一行不能同时用、构造器链先父后子父类无无参时子类必须显式 super(参数)」。