Java 类加载与初始化
2026-06-27 00:15:35 | 新服速递 | admin | 776°c
吃透 Java 类加载与初始化
在 Java 开发中,类的加载与初始化流程(静态块、构造器、父子类继承执行顺序)是绕不开的基础,也是面试高频考点。本文结合多组实战案例,带你穿透 “类加载阶段” 与 “对象创建阶段” 的执行逻辑,彻底掌握核心规则!
一、核心概念:类加载与初始化的两大阶段
Java 中,类从 “被使用” 到 “可用” 需经历 “类加载” 和 “对象创建” 两大阶段,每个阶段执行不同代码逻辑:
1. 类加载阶段(触发条件)
首次使用类的静态资源(如静态变量、静态方法、创建对象等)时触发。
执行内容:
初始化静态变量(static int count;)。
执行静态代码块(static {})。
特点:全局仅执行一次(无论创建多少对象,类加载只发生一次)。
2. 对象创建阶段(触发条件)
执行 new 类名() 创建对象时触发。
执行内容:
先执行非静态代码块({},也叫实例代码块 )。
再执行构造器(类名())。
特点:每次 new 对象时都会执行(非静态块和构造器与对象绑定)。
3. 父子类继承的 “叠加规则”
若存在继承关系(子类 extends 父类),执行顺序会严格遵循 “先父类、后子类”:
类加载阶段:先加载父类 → 执行父类静态块 → 再加载子类 → 执行子类静态块。
对象创建阶段:先执行父类非静态块 → 父类构造器 → 再执行子类非静态块 → 子类构造器。
二、实战案例拆解:从基础到继承
下面通过典型案例,用 “执行顺序” 逻辑逐场景分析,其他案例可直接套用这套思路。
案例 1:基础类的执行顺序(静态块 + 非静态块 + 构造器)
代码结构(简化版):
class Person {
// 非静态块:对象创建时执行
{ System.out.println("非静态块"); }
// 静态块:类加载时执行
static { System.out.println("静态块"); }
// 构造器:对象创建时执行(非静态块之后)
public Person() {
System.out.println("构造");
}
}
public class Demo02 {
public static void main(String[] args) {
// 创建对象 → 触发类加载 + 对象创建
Person p1 = new Person();
}
}
执行流程拆解:
main 中执行 new Person() → 触发 Person 类加载。
类加载阶段:执行 Person 的静态块 → 输出 静态块。
对象创建阶段:
先执行非静态块 → 输出 非静态块。
再执行构造器 → 输出 构造。
最终输出:
静态块
非静态块
构造
案例 2:静态变量的 “声明与赋值” 顺序
代码(注意静态变量和静态块的顺序):
class Person {
static int count; // 静态变量声明(默认值 0)
// 静态块:类加载时执行
static {
count = 0; // 给静态变量赋值
System.out.println("静态块");
}
}
public class Demo02 {
public static void main(String[] args) {
// 访问静态变量 → 触发类加载
System.out.println(Person.count);
}
}
执行逻辑:
main 中访问 Person.count → 触发 Person 类加载。
类加载阶段:
先 “声明” 静态变量 count(默认值 0)。
再执行静态块 → 给 count 赋值 0(代码执行,但值未变),输出 静态块。
类加载完成后,访问 Person.count → 输出 0。
关键规则:
静态变量的 “声明” 早于静态块执行(即使代码顺序上静态块在前,JVM 也会优先处理变量声明)。若静态块中使用未声明的变量,会直接编译报错!
案例 3:父子类继承的复杂执行顺序
代码结构(子类 Son 继承父类 Person):
class Person {
// 父类静态块:类加载时执行
static { System.out.println("Person静态块"); }
// 父类非静态块:对象创建时执行
{ System.out.println("Person非静态块"); }
// 父类构造器:对象创建时执行(非静态块之后)
public Person() {
System.out.println("Person构造");
}
}
class Son extends Person {
// 子类静态块:类加载时执行(父类之后)
static { System.out.println("Son静态块"); }
// 子类非静态块:对象创建时执行(父类之后)
{ System.out.println("Son非静态块"); }
// 子类构造器:对象创建时执行(非静态块之后)
public Son() {
System.out.println("Son构造");
}
}
public class Demo02 {
public static void main(String[] args) {
// 创建子类对象 → 触发父类+子类的类加载 + 对象创建
Son s = new Son();
}
}
执行流程拆解(按阶段拆分):
阶段
执行内容
输出结果
父类加载阶段
执行父类静态块
Person静态块
子类加载阶段
执行子类静态块
Son静态块
父类对象阶段
执行父类非静态块 → 父类构造器
Person非静态块 → Person构造
子类对象阶段
执行子类非静态块 → 子类构造器
Son非静态块 → Son构造
最终输出顺序:
Person静态块
Son静态块
Person非静态块
Person构造
Son非静态块
Son构造
案例 4:静态变量修改与 “类加载触发条件”
代码(重点看 “访问静态变量是否触发类加载”):
class Person {
static int count = 0; // 静态变量声明+默认赋值
// 静态块:类加载时执行
static {
count = 10; // 重新赋值
System.out.println("Person静态块");
}
}
public class Demo02 {
public static void main(String[] args) {
// 情况1:直接访问静态变量 → 触发类加载
System.out.println(Person.count);
// 情况2:修改静态变量 → 若类未加载过,先触发类加载
Person.count = 20;
}
}
执行逻辑:
情况1:访问 Person.count → 触发 Person 类加载 → 执行静态块(count 赋值为 10,输出 Person静态块)→ 输出 10。
情况2:修改 Person.count → 若 Person 类已加载(因 情况1 已加载),直接修改;若未加载过,会先触发类加载(执行静态块),再修改。
三、高频问题 & 避坑点
掌握核心流程后,这些常见问题就能迎刃而解:
1. 静态块里能访问 “后面声明的静态变量” 吗?
可以 声明,但 不能 “读取/使用” 未声明的变量。
class Test {
static {
int a; // 合法:声明变量(JVM 会提前处理)
count = 10; // 合法:给已声明的变量赋值
// System.out.println(a); // 非法:读取未声明的变量(编译报错)
}
static int count; // 变量声明
}
2. 静态块执行几次?
全局仅执行一次(类加载只发生一次,与 new 对象次数无关)。
3. 子类访问父类静态变量,会触发父类加载吗?
会!只要用到父类的静态资源(变量、方法、创建对象等),都会触发父类的类加载。
四、总结:记住 “2 阶段 3 顺序”
掌握类加载与初始化,核心记住 “2 阶段 3 顺序”:
1. 两大阶段
类加载阶段:执行静态块、静态变量初始化(全局 1 次)。
对象创建阶段:执行非静态块、构造器(每次 new 都执行)。
2. 三大顺序
继承场景类加载:先父类 → 后子类。
继承场景对象创建:先父类非静态块/构造器 → 后子类非静态块/构造器。
静态变量与静态块:变量声明早于静态块执行(JVM 隐式调整顺序)。
理解这套规则后,再复杂的继承、静态块案例,都能按 “阶段拆分 → 父类/子类顺序 → 静态/非静态区分” 分析。从此类加载与初始化不再绕,面试 & 开发都能游刃有余!
如果觉得内容有帮助,欢迎点赞、收藏~ 有疑问或想深挖的点,评论区见!