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 隐式调整顺序)。

理解这套规则后,再复杂的继承、静态块案例,都能按 “阶段拆分 → 父类/子类顺序 → 静态/非静态区分” 分析。从此类加载与初始化不再绕,面试 & 开发都能游刃有余!

如果觉得内容有帮助,欢迎点赞、收藏~ 有疑问或想深挖的点,评论区见!