Java类初始化顺序(超详细)

Java类初始化顺序(超详细)

📃个人主页:小韩学长yyds-CSDN博客

⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞

箴言:拥有耐心才是生活的关键

目录

介绍

Java 类初始化顺序理论知识

静态变量和静态初始化块

实例变量和非静态初始化块

构造函数

静态方法

代码示例与分析

示例代码展示

代码执行结果分析

有继承关系的类初始化顺序

继承关系下的初始化规则

示例代码及分析

绘制流程图

无继承关系类的初始化流程图

有继承关系类的初始化流程图

介绍

在 Java 编程中,类的初始化顺序是一个基础且重要的知识点。理解类的初始化顺序,有助于我们更好地掌握 Java 的运行机制,编写出更健壮、更高效的代码。无论是在日常开发中,还是在面试过程中,类的初始化顺序相关问题都经常出现。比如,在涉及到复杂的继承关系和静态资源管理时,如果对初始化顺序不了解,可能会导致程序出现意想不到的错误。接下来,让我们深入探讨 Java 中类的初始化顺序,揭开静态方法、静态块、非静态块在其中扮演的角色。

Java 类初始化顺序理论知识

静态变量和静态初始化块

当类被加载到 JVM 时(即第一次主动使用该类时,如创建类的实例、访问类的静态变量或静态方法等),类的静态变量和静态初始化块会被初始化。静态初始化块(也称为静态代码块)在静态变量初始化之后、创建类的任何实例之前执行。如果有多个静态初始化块,它们会按照在源代码中出现的顺序执行。静态变量和静态初始化块只会在类被加载时执行一次 。

例如,以下代码展示了静态变量和静态初始化块的执行顺序:

public class StaticInitExample {

// 静态变量

private static int staticVar = initializeStaticVar();

// 静态初始化块

static {

System.out.println("静态初始化块执行");

}

// 静态方法,用于初始化静态变量

public static int initializeStaticVar() {

System.out.println("静态方法initializeStaticVar执行");

return 1;

}

public static void main(String[] args) {

// 第一次主动使用类,触发类的加载和静态初始化

System.out.println("静态变量staticVar的值:" + staticVar);

}

}

运行上述代码,输出结果为:

静态方法initializeStaticVar执行

静态初始化块执行

静态变量staticVar的值:1

可以看到,静态方法initializeStaticVar先执行,为静态变量staticVar赋值,然后执行静态初始化块,最后在main方法中打印静态变量的值。

实例变量和非静态初始化块

当创建类的实例时,实例变量和非静态初始化块(也称为实例初始化块或初始化块)会被初始化。如果一个类有多个非静态初始化块,它们会按照在源代码中出现的顺序执行。非静态初始化块在实例变量初始化之后、类的构造函数之前执行。每次创建类的实例时,都会执行非静态初始化块。

例如:

public class InstanceInitExample {

// 实例变量

private int instanceVar;

// 非静态初始化块

{

System.out.println("非静态初始化块执行");

instanceVar = 42;

}

// 构造函数

public InstanceInitExample() {

System.out.println("构造函数执行");

}

public static void main(String[] args) {

// 创建类的实例,触发实例初始化和构造函数

InstanceInitExample example = new InstanceInitExample();

}

}

运行结果为:

非静态初始化块执行

构造函数执行

从结果可知,创建实例时,先执行非静态初始化块,再执行构造函数。

构造函数

在非静态初始化块执行完毕后,会调用类的构造函数。构造函数用于初始化实例的状态,并在每次创建类的实例时执行。如果有多个构造函数,可以根据传入的参数来选择使用哪一个。如果在类的定义中没有显式地提供构造函数,编译器会提供一个默认的构造函数。

构造函数的主要作用是完成对类对象的初始化工作,它的方法名与类名相同,并且没有返回类型。比如在以下代码中:

public class ConstructorExample {

private int value;

// 构造函数

public ConstructorExample(int v) {

value = v;

System.out.println("构造函数初始化value为:" + value);

}

public static void main(String[] args) {

ConstructorExample example = new ConstructorExample(10);

}

}

运行结果为:

构造函数初始化value为:10

这里通过构造函数,在创建对象时对value进行了初始化。

静态方法

静态方法属于类本身,而不是类的实例。静态方法可以通过类名直接调用,无需创建类的实例。静态方法不能访问类的非静态成员(包括非静态变量和非静态方法),除非通过类的实例来访问。静态方法的执行不依赖于类的初始化过程。它们可以在类被加载之前、之后或类的实例被创建之前、之后调用。

比如:

public class StaticMethodExample {

private static int staticVar = 10;

private int instanceVar = 20;

// 静态方法

public static void staticMethod() {

System.out.println("静态方法执行,静态变量staticVar的值为:" + staticVar);

// 以下代码会报错,因为静态方法不能直接访问非静态变量

// System.out.println("非静态变量instanceVar的值为:" + instanceVar);

}

public void instanceMethod() {

System.out.println("实例方法执行,静态变量staticVar的值为:" + staticVar);

System.out.println("实例方法执行,非静态变量instanceVar的值为:" + instanceVar);

}

public static void main(String[] args) {

// 直接调用静态方法,无需创建实例

StaticMethodExample.staticMethod();

StaticMethodExample example = new StaticMethodExample();

example.instanceMethod();

}

}

运行结果为:

静态方法执行,静态变量staticVar的值为:10

实例方法执行,静态变量staticVar的值为:10

实例方法执行,非静态变量instanceVar的值为:20

可以看到,静态方法staticMethod可以直接通过类名调用,并且只能访问静态变量;而实例方法instanceMethod需要通过对象实例调用,能访问静态变量和非静态变量。

代码示例与分析

示例代码展示

为了更直观地理解 Java 中类的初始化顺序,我们来看一个综合的代码示例:

public class InitializationOrderExample {

// 静态变量

private static int staticVar = 10;

// 静态初始化块

static {

System.out.println("静态初始化块,静态变量staticVar的值:" + staticVar);

staticVar = 20;

System.out.println("静态初始化块修改后,静态变量staticVar的值:" + staticVar);

}

// 实例变量

private int instanceVar = 30;

// 非静态初始化块

{

System.out.println("非静态初始化块,实例变量instanceVar的值:" + instanceVar);

instanceVar = 40;

System.out.println("非静态初始化块修改后,实例变量instanceVar的值:" + instanceVar);

}

// 构造函数

public InitializationOrderExample() {

System.out.println("构造函数,实例变量instanceVar的值:" + instanceVar);

instanceVar = 50;

System.out.println("构造函数修改后,实例变量instanceVar的值:" + instanceVar);

}

// 静态方法

public static void staticMethod() {

System.out.println("静态方法执行,静态变量staticVar的值:" + staticVar);

}

public static void main(String[] args) {

// 调用静态方法,触发类的加载和静态初始化

InitializationOrderExample.staticMethod();

// 创建类的实例,触发实例初始化和构造函数

InitializationOrderExample example = new InitializationOrderExample();

}

}

代码执行结果分析

静态部分初始化:当程序执行到InitializationOrderExample.staticMethod()时,类InitializationOrderExample被加载到 JVM 中,开始静态初始化。首先,静态变量staticVar被初始化为 10,然后执行静态初始化块。在静态初始化块中,先打印出staticVar的值为 10,接着将其修改为 20,并再次打印。此时,静态初始化完成,staticVar的值为 20 。实例部分初始化:当执行new InitializationOrderExample()时,开始实例初始化。首先,实例变量instanceVar被初始化为 30,然后执行非静态初始化块。在非静态初始化块中,先打印出instanceVar的值为 30,接着将其修改为 40,并再次打印。最后,执行构造函数。在构造函数中,先打印出instanceVar的值为 40,接着将其修改为 50,并再次打印。

完整的输出结果如下:

静态方法执行,静态变量staticVar的值:20

静态初始化块,静态变量staticVar的值:10

静态初始化块修改后,静态变量staticVar的值:20

非静态初始化块,实例变量instanceVar的值:30

非静态初始化块修改后,实例变量instanceVar的值:40

构造函数,实例变量instanceVar的值:40

构造函数修改后,实例变量instanceVar的值:50

通过这个示例和分析,我们可以清晰地看到 Java 类中静态变量、静态初始化块、实例变量、非静态初始化块和构造函数的初始化顺序,以及它们之间的相互作用 。这对于我们编写正确、高效的 Java 代码非常重要,尤其是在处理复杂的类结构和对象初始化逻辑时。

有继承关系的类初始化顺序

继承关系下的初始化规则

在 Java 中,当存在继承关系时,类的初始化顺序变得更加复杂,但也遵循严格的规则。首先,父类的静态变量和静态初始化块会被初始化,这是因为静态成员属于类本身,在类被加载时就需要准备好。而且静态成员只会在类首次加载时初始化一次。接着,子类的静态变量和静态初始化块会被初始化。

当创建子类的实例时,会先初始化父类的非静态变量和非静态初始化块,为父类实例的状态做准备。然后调用父类的构造函数,完成父类实例的初始化。之后,再初始化子类的非静态变量和非静态初始化块,最后调用子类的构造函数 。这个顺序确保了在子类实例化之前,父类已经被正确地初始化,子类可以依赖父类的初始化结果。

示例代码及分析

下面是一个展示有继承关系的类初始化顺序的示例代码:

class ParentClass {

// 父类静态变量

private static int parentStaticVar = 100;

// 父类静态初始化块

static {

System.out.println("父类静态初始化块,父类静态变量parentStaticVar的值:" + parentStaticVar);

parentStaticVar = 200;

System.out.println("父类静态初始化块修改后,父类静态变量parentStaticVar的值:" + parentStaticVar);

}

// 父类实例变量

private int parentInstanceVar = 300;

// 父类非静态初始化块

{

System.out.println("父类非静态初始化块,父类实例变量parentInstanceVar的值:" + parentInstanceVar);

parentInstanceVar = 400;

System.out.println("父类非静态初始化块修改后,父类实例变量parentInstanceVar的值:" + parentInstanceVar);

}

// 父类构造函数

public ParentClass() {

System.out.println("父类构造函数,父类实例变量parentInstanceVar的值:" + parentInstanceVar);

parentInstanceVar = 500;

System.out.println("父类构造函数修改后,父类实例变量parentInstanceVar的值:" + parentInstanceVar);

}

}

class ChildClass extends ParentClass {

// 子类静态变量

private static int childStaticVar = 600;

// 子类静态初始化块

static {

System.out.println("子类静态初始化块,子类静态变量childStaticVar的值:" + childStaticVar);

childStaticVar = 700;

System.out.println("子类静态初始化块修改后,子类静态变量childStaticVar的值:" + childStaticVar);

}

// 子类实例变量

private int childInstanceVar = 800;

// 子类非静态初始化块

{

System.out.println("子类非静态初始化块,子类实例变量childInstanceVar的值:" + childInstanceVar);

childInstanceVar = 900;

System.out.println("子类非静态初始化块修改后,子类实例变量childInstanceVar的值:" + childInstanceVar);

}

// 子类构造函数

public ChildClass() {

System.out.println("子类构造函数,子类实例变量childInstanceVar的值:" + childInstanceVar);

childInstanceVar = 1000;

System.out.println("子类构造函数修改后,子类实例变量childInstanceVar的值:" + childInstanceVar);

}

}

public class InheritanceInitializationExample {

public static void main(String[] args) {

// 创建子类实例,触发类的加载和初始化

ChildClass child = new ChildClass();

}

}

静态部分初始化:当程序执行到new ChildClass()时,ChildClass类被加载到 JVM 中,由于它继承自ParentClass,所以先加载ParentClass。ParentClass的静态变量parentStaticVar被初始化为 100,然后执行其静态初始化块,在块中先打印parentStaticVar的值为 100,再修改为 200 并打印。接着,ChildClass的静态变量childStaticVar被初始化为 600,执行其静态初始化块,先打印childStaticVar的值为 600,再修改为 700 并打印。实例部分初始化:开始创建ChildClass的实例,先初始化ParentClass的实例部分。ParentClass的实例变量parentInstanceVar被初始化为 300,执行其非静态初始化块,先打印parentInstanceVar的值为 300,再修改为 400 并打印。然后调用ParentClass的构造函数,先打印parentInstanceVar的值为 400,再修改为 500 并打印。接着,初始化ChildClass的实例部分,ChildClass的实例变量childInstanceVar被初始化为 800,执行其非静态初始化块,先打印childInstanceVar的值为 800,再修改为 900 并打印。最后,调用ChildClass的构造函数,先打印childInstanceVar的值为 900,再修改为 1000 并打印。

完整的输出结果如下:

父类静态初始化块,父类静态变量parentStaticVar的值:100

父类静态初始化块修改后,父类静态变量parentStaticVar的值:200

子类静态初始化块,子类静态变量childStaticVar的值:600

子类静态初始化块修改后,子类静态变量childStaticVar的值:700

父类非静态初始化块,父类实例变量parentInstanceVar的值:300

父类非静态初始化块修改后,父类实例变量parentInstanceVar的值:400

父类构造函数,父类实例变量parentInstanceVar的值:400

父类构造函数修改后,父类实例变量parentInstanceVar的值:500

子类非静态初始化块,子类实例变量childInstanceVar的值:800

子类非静态初始化块修改后,子类实例变量childInstanceVar的值:900

子类构造函数,子类实例变量childInstanceVar的值:900

子类构造函数修改后,子类实例变量childInstanceVar的值:1000

通过这个示例和分析,我们可以清晰地看到在继承关系下,Java 类的静态部分和非静态部分是如何按照顺序进行初始化的 。这对于理解 Java 的面向对象特性和编写正确的代码非常重要,特别是在处理复杂的继承体系时,能帮助我们避免很多潜在的错误。

绘制流程图

无继承关系类的初始化流程图

为了更直观地展示无继承关系类的初始化顺序,我们可以绘制如下流程图:

在这个流程图中,首先类被加载到 JVM,然后静态变量被初始化,接着执行静态初始化块。如果创建类的实例,那么会初始化实例变量,执行非静态初始化块,最后调用构造函数。如果不创建实例,流程结束 。

有继承关系类的初始化流程图

对于有继承关系的类,初始化顺序的流程图如下:

在有继承关系的情况下,先加载父类到 JVM,完成父类静态变量的初始化和静态初始化块的执行,然后加载子类到 JVM,进行子类静态部分的初始化。当创建子类实例时,先初始化父类的实例部分,调用父类构造函数,再初始化子类的实例部分,最后调用子类构造函数 。如果不创建子类实例,流程结束。通过这两个流程图,我们可以更清晰地理解 Java 中类的初始化顺序,无论是在无继承关系还是有继承关系的情况下。

结语

🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下博主~

相关推荐

鬼谷子译文哪个版本好?权威版本推荐与深度解析
office365ios版本

鬼谷子译文哪个版本好?权威版本推荐与深度解析

📅 08-30 👁️ 1942
外科风云庄恕的车是什么牌子的 庄恕开的车价值多少钱
365BET体育投注官网

外科风云庄恕的车是什么牌子的 庄恕开的车价值多少钱

📅 08-04 👁️ 1383
豪猪有什么特征?豪猪的天敌有哪些
office365ios版本

豪猪有什么特征?豪猪的天敌有哪些

📅 10-19 👁️ 2060
游泳世界杯
hse365平台

游泳世界杯

📅 07-16 👁️ 9082