第20篇:Java初始化、构造器、对象创建的过程
📌 系列导航 :《Java 100 天进阶之路》完整目录 |
⬅️ 上一篇:第19篇:Java接口的作用和意义 |
➡️ 下一篇:第21篇:Java Object类
一、核心知识点
- 成员初始化顺序:
- 静态成员(静态变量、静态代码块)在类加载时执行,按声明顺序执行
- 实例成员(实例变量、实例代码块)在创建对象时执行,在构造方法之前执行
- 构造方法最后执行
- 构造器(构造方法)的作用:初始化对象状态
- 对象创建的完整流程(JVM 角度):
- 类加载检查(如果类未加载,先加载)
- 堆中分配内存
- 设置默认值(零值)
- 设置对象头(类型指针、GC 信息等)
- 执行实例初始化:实例变量显式赋值 → 实例代码块 → 构造方法
- 父子类初始化顺序:先父类静态 → 子类静态 → 父类实例变量/代码块 → 父类构造 → 子类实例变量/代码块 → 子类构造
二、通俗讲解(1分钟开心学)
1. 初始化过程就像"盖房子"
- 类加载:先设计图纸(静态成员),只做一次。
- 对象创建:每次建房,都要打地基(默认值)、砌墙(显式赋值)、内部装修(实例代码块)、最后交房(构造方法)。
2. 静态成分(类级别)
当你第一次使用一个类时(new、访问静态变量/方法、Class.forName等),JVM 会加载这个类,并执行:
- 静态变量赋值
- 静态代码块(按代码顺序)
3. 实例成分(对象级别)
每次 new 一个对象,都会执行:
- 实例变量显式赋值
- 实例代码块(
{ ... }中的内容,按顺序) - 构造方法
4. 继承时的顺序
想象你创建子类对象,必须先给父类"打好地基":
父类静态 → 子类静态 → 父类实例初始化 → 父类构造 → 子类实例初始化 → 子类构造
面试口诀 :
"父静子静,父实父构,子实子构"
三、实操代码案例 + 场景说明
场景:通过打印日志,直观看到初始化的完整顺序。
1. 单个类的初始化顺序
java
class Student {
private String name = initName(); // 实例变量显式赋值
private static int counter = initCounter(); // 静态变量
static {
System.out.println("2. 静态代码块执行");
}
{
System.out.println("4. 实例代码块执行");
}
public Student() {
System.out.println("5. 构造方法执行");
}
private static int initCounter() {
System.out.println("1. 静态变量赋值 (initCounter)");
return 0;
}
private String initName() {
System.out.println("3. 实例变量赋值 (initName)");
return "默认";
}
}
// 执行 new Student() 输出:
// 1. 静态变量赋值
// 2. 静态代码块执行
// 3. 实例变量赋值
// 4. 实例代码块执行
// 5. 构造方法执行
2. 继承时的完整顺序
java
class Parent {
static { System.out.println("A. 父类静态块"); }
{ System.out.println("C. 父类实例块"); }
Parent() { System.out.println("D. 父类构造方法"); }
}
class Child extends Parent {
static { System.out.println("B. 子类静态块"); }
{ System.out.println("E. 子类实例块"); }
Child() { System.out.println("F. 子类构造方法"); }
}
// 执行 new Child() 输出:
// A. 父类静态块
// B. 子类静态块
// C. 父类实例块
// D. 父类构造方法
// E. 子类实例块
// F. 子类构造方法
3. 对象创建的内存流程(代码配合注释)
java
public class MemoryProcess {
private int value = 100; // ① 默认值0 → ② 显式赋值为100
private String text; // 默认null,没有显式赋值
public MemoryProcess(String text) {
this.text = text; // ③ 构造方法中赋值
}
}
// 执行 new MemoryProcess("hello"):
// 1. 堆中分配内存
// 2. value = 0, text = null (默认值)
// 3. value = 100 (显式赋值)
// 4. text = "hello" (构造方法)
四、避坑要点
| 错误/误区 | 后果 | 正确做法 |
|---|---|---|
| 在静态代码块中访问实例成员 | 编译错误 | 静态上下文只能访问静态成员 |
| 实例代码块中抛出未捕获异常 | 对象创建失败,异常传播给调用者 | 尽量不在实例代码块中写复杂逻辑 |
| 构造方法中调用可被子类重写的方法 | 子类未初始化完成,可能空指针 | 只调用 private 或 final 方法 |
this() 和 super() 同时出现在构造方法中 |
编译错误 | 只能选其一,且必须在第一行 |
五、面试高频考点
Q1:静态代码块、实例代码块、构造方法的执行顺序?
类加载:静态代码块。对象创建:实例变量显式赋值和实例代码块(按顺序) → 构造方法。继承时先父类后子类。
Q2:一个对象创建时,内存中发生了什么?
① 类加载(如果没加载)→ ② 堆中分配内存 → ③ 成员变量设默认值 → ④ 设置对象头 → ⑤ 实例初始化(显式赋值、实例代码块、构造方法)。
Q3:构造方法中调用普通方法有什么风险?
如果该方法被子类重写,调用时会执行子类版本,而此时子类的成员可能尚未初始化(null 或 0),导致逻辑错误或空指针。
六、练习题
-
预测输出 :
javaclass A { static { System.out.print("A1 "); } { System.out.print("A2 "); } A() { System.out.print("A3 "); } } class B extends A { static { System.out.print("B1 "); } { System.out.print("B2 "); } B() { System.out.print("B3 "); } } public class Test { public static void main(String[] args) { new B(); } } -
代码改错 :下面代码哪里有问题?如何修正?
javaclass Base { public Base() { print(); } void print() { System.out.println("Base"); } } class Derived extends Base { private String value = "Hello"; @Override void print() { System.out.println(value.toUpperCase()); } } -
动手 :写一个类,包含静态代码块、实例代码块、构造方法,并在
main中创建多个对象,观察输出。
📊 你的学习进度
- 当前:第20篇 / 共44篇 · 第二阶段:核心语法与面向对象(第5~20篇)
- ✅ 已完成:第1~19篇
- 📖 正在学:第20篇
- ⏳ 待学习:第21~44篇
👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇
💡 本专栏每篇都包含:避坑表 + 面试高频考点 + 练习题。每天30分钟,100天拿offer!
👉 下一篇预告
《Java Object类》
内容简介:toString、equals、hashCode的正确重写规范,equals与hashCode的契约,getClass、clone方法。
💡 学完这篇,你将掌握所有类的共同"祖先",写出规范的Java Bean。
📌 《Java 100 天进阶之路 | 从入门到上岗就业》 每天一篇,建议收藏 + 关注 ,一起100天拿offer!
👉 点击关注我,更新后第一时间收到推送!